Commit d0f6d401 authored by Dave Airlie's avatar Dave Airlie

Merge tag 'drm-misc-next-2017-10-12' of git://anongit.freedesktop.org/drm/drm-misc into drm-next

More 4.15 drm-misc stuff:

Cross-subsystem Changes:
- bridge cleanup refactor (Benjamin Gaignard)

Core Changes:
- less surprising atomic iterators (Maarten), fixes an oops introduced
  in drm-next
- better gem/fb helper docs (Noralf)
- fix dma-buf rcu races (Christian König)

Driver Changes:
- adv7511: CEC support (Hans Verkuil)
- sun4i update from Chen-Yu to improve hdmi and A31 support
- sii8620: add remote control support (Maceiej Purski)

New drivers:
- SiI9234 bridge driver (Maciej Purski)
- 7" rpi touch panel (Eric Anholt)

Note that this contains a topic pull from regmap, needed by the sun4i
changes. Mark Brown sent that out for pulling into drm-misc.

* tag 'drm-misc-next-2017-10-12' of git://anongit.freedesktop.org/drm/drm-misc: (29 commits)
  drm/dp: WARN about invalid/unknown link rates and bw codes
  drm/msm/mdp5: remove less than 0 comparison for unsigned value
  drm/bridge/sii8620: add remote control support
  drm/sun4i: hdmi: Add support for A31's HDMI controller
  drm/sun4i: hdmi: Add A31 specific DDC register definitions
  drm/sun4i: hdmi: Add support for controller hardware variants
  dt-bindings: display: sun4i: Add binding for A31 HDMI controller
  drm/sun4i: hdmi: Allow using second PLL as TMDS clk parent
  drm/sun4i: hdmi: create a regmap for later use
  drm/sun4i: hdmi: Disable clks in bind function error path and unbind function
  drm/sun4i: tcon: Add support for demuxing TCON output on A31
  drm/sun4i: tcon: Add variant callback for TCON output muxing
  drm/bridge/synopsys: dsi :remove is_panel_bridge
  drm/vc4: remove bridge from driver internal structure
  drm/stm: ltdc: remove bridge from driver internal structure
  drm/drm_of: add drm_of_panel_bridge_remove function
  drm/bridge: make drm_panel_bridge_remove more robust
  dma-fence: fix dma_fence_get_rcu_safe v2
  dma-buf: make reservation_object_copy_fences rcu save
  drm/atomic: Unref duplicated drm_atomic_state in drm_atomic_helper_resume()
  ...
parents 25e1a798 cccf4e3f
...@@ -68,6 +68,8 @@ Optional properties: ...@@ -68,6 +68,8 @@ Optional properties:
- adi,disable-timing-generator: Only for ADV7533. Disables the internal timing - adi,disable-timing-generator: Only for ADV7533. Disables the internal timing
generator. The chip will rely on the sync signals in the DSI data lanes, generator. The chip will rely on the sync signals in the DSI data lanes,
rather than generate its own timings for HDMI output. rather than generate its own timings for HDMI output.
- clocks: from common clock binding: reference to the CEC clock.
- clock-names: from common clock binding: must be "cec".
Required nodes: Required nodes:
...@@ -89,6 +91,8 @@ Example ...@@ -89,6 +91,8 @@ Example
reg = <39>; reg = <39>;
interrupt-parent = <&gpio3>; interrupt-parent = <&gpio3>;
interrupts = <29 IRQ_TYPE_EDGE_FALLING>; interrupts = <29 IRQ_TYPE_EDGE_FALLING>;
clocks = <&cec_clock>;
clock-names = "cec";
adi,input-depth = <8>; adi,input-depth = <8>;
adi,input-colorspace = "rgb"; adi,input-colorspace = "rgb";
......
Silicon Image SiI9234 HDMI/MHL bridge bindings
Required properties:
- compatible : "sil,sii9234".
- reg : I2C address for TPI interface, use 0x39
- avcc33-supply : MHL/USB Switch Supply Voltage (3.3V)
- iovcc18-supply : I/O Supply Voltage (1.8V)
- avcc12-supply : TMDS Analog Supply Voltage (1.2V)
- cvcc12-supply : Digital Core Supply Voltage (1.2V)
- interrupts, interrupt-parent: interrupt specifier of INT pin
- reset-gpios: gpio specifier of RESET pin (active low)
- video interfaces: Device node can contain two video interface port
nodes for HDMI encoder and connector according to [1].
- port@0 - MHL to HDMI
- port@1 - MHL to connector
[1]: Documentation/devicetree/bindings/media/video-interfaces.txt
Example:
sii9234@39 {
compatible = "sil,sii9234";
reg = <0x39>;
avcc33-supply = <&vcc33mhl>;
iovcc18-supply = <&vcc18mhl>;
avcc12-supply = <&vsil12>;
cvcc12-supply = <&vsil12>;
reset-gpios = <&gpf3 4 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpf3>;
interrupts = <5 IRQ_TYPE_LEVEL_HIGH>;
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
mhl_to_hdmi: endpoint {
remote-endpoint = <&hdmi_to_mhl>;
};
};
port@1 {
reg = <1>;
mhl_to_connector: endpoint {
remote-endpoint = <&connector_to_mhl>;
};
};
};
};
This binding covers the official 7" (800x480) Raspberry Pi touchscreen
panel.
This DSI panel contains:
- TC358762 DSI->DPI bridge
- Atmel microcontroller on I2C for power sequencing the DSI bridge and
controlling backlight
- Touchscreen controller on I2C for touch input
and this binding covers the DSI display parts but not its touch input.
Required properties:
- compatible: Must be "raspberrypi,7inch-touchscreen-panel"
- reg: Must be "45"
- port: See panel-common.txt
Example:
dsi1: dsi@7e700000 {
#address-cells = <1>;
#size-cells = <0>;
<...>
port {
dsi_out_port: endpoint {
remote-endpoint = <&panel_dsi_port>;
};
};
};
i2c_dsi: i2c {
compatible = "i2c-gpio";
#address-cells = <1>;
#size-cells = <0>;
gpios = <&gpio 28 0
&gpio 29 0>;
lcd@45 {
compatible = "raspberrypi,7inch-touchscreen-panel";
reg = <0x45>;
port {
panel_dsi_port: endpoint {
remote-endpoint = <&dsi_out_port>;
};
};
};
};
...@@ -41,14 +41,17 @@ CEC. It is one end of the pipeline. ...@@ -41,14 +41,17 @@ CEC. It is one end of the pipeline.
Required properties: Required properties:
- compatible: value must be one of: - compatible: value must be one of:
* allwinner,sun5i-a10s-hdmi * allwinner,sun5i-a10s-hdmi
* allwinner,sun6i-a31-hdmi
- reg: base address and size of memory-mapped region - reg: base address and size of memory-mapped region
- interrupts: interrupt associated to this IP - interrupts: interrupt associated to this IP
- clocks: phandles to the clocks feeding the HDMI encoder - clocks: phandles to the clocks feeding the HDMI encoder
* ahb: the HDMI interface clock * ahb: the HDMI interface clock
* mod: the HDMI module clock * mod: the HDMI module clock
* ddc: the HDMI ddc clock (A31 only)
* pll-0: the first video PLL * pll-0: the first video PLL
* pll-1: the second video PLL * pll-1: the second video PLL
- clock-names: the clock names mentioned above - clock-names: the clock names mentioned above
- resets: phandle to the reset control for the HDMI encoder (A31 only)
- dmas: phandles to the DMA channels used by the HDMI encoder - dmas: phandles to the DMA channels used by the HDMI encoder
* ddc-tx: The channel for DDC transmission * ddc-tx: The channel for DDC transmission
* ddc-rx: The channel for DDC reception * ddc-rx: The channel for DDC reception
......
...@@ -266,8 +266,7 @@ EXPORT_SYMBOL(reservation_object_add_excl_fence); ...@@ -266,8 +266,7 @@ EXPORT_SYMBOL(reservation_object_add_excl_fence);
* @dst: the destination reservation object * @dst: the destination reservation object
* @src: the source reservation object * @src: the source reservation object
* *
* Copy all fences from src to dst. Both src->lock as well as dst-lock must be * Copy all fences from src to dst. dst-lock must be held.
* held.
*/ */
int reservation_object_copy_fences(struct reservation_object *dst, int reservation_object_copy_fences(struct reservation_object *dst,
struct reservation_object *src) struct reservation_object *src)
...@@ -277,33 +276,62 @@ int reservation_object_copy_fences(struct reservation_object *dst, ...@@ -277,33 +276,62 @@ int reservation_object_copy_fences(struct reservation_object *dst,
size_t size; size_t size;
unsigned i; unsigned i;
src_list = reservation_object_get_list(src); rcu_read_lock();
src_list = rcu_dereference(src->fence);
retry:
if (src_list) { if (src_list) {
size = offsetof(typeof(*src_list), unsigned shared_count = src_list->shared_count;
shared[src_list->shared_count]);
size = offsetof(typeof(*src_list), shared[shared_count]);
rcu_read_unlock();
dst_list = kmalloc(size, GFP_KERNEL); dst_list = kmalloc(size, GFP_KERNEL);
if (!dst_list) if (!dst_list)
return -ENOMEM; return -ENOMEM;
dst_list->shared_count = src_list->shared_count; rcu_read_lock();
dst_list->shared_max = src_list->shared_count; src_list = rcu_dereference(src->fence);
for (i = 0; i < src_list->shared_count; ++i) if (!src_list || src_list->shared_count > shared_count) {
dst_list->shared[i] = kfree(dst_list);
dma_fence_get(src_list->shared[i]); goto retry;
}
dst_list->shared_count = 0;
dst_list->shared_max = shared_count;
for (i = 0; i < src_list->shared_count; ++i) {
struct dma_fence *fence;
fence = rcu_dereference(src_list->shared[i]);
if (test_bit(DMA_FENCE_FLAG_SIGNALED_BIT,
&fence->flags))
continue;
if (!dma_fence_get_rcu(fence)) {
kfree(dst_list);
src_list = rcu_dereference(src->fence);
goto retry;
}
if (dma_fence_is_signaled(fence)) {
dma_fence_put(fence);
continue;
}
dst_list->shared[dst_list->shared_count++] = fence;
}
} else { } else {
dst_list = NULL; dst_list = NULL;
} }
new = dma_fence_get_rcu_safe(&src->fence_excl);
rcu_read_unlock();
kfree(dst->staged); kfree(dst->staged);
dst->staged = NULL; dst->staged = NULL;
src_list = reservation_object_get_list(dst); src_list = reservation_object_get_list(dst);
old = reservation_object_get_excl(dst); old = reservation_object_get_excl(dst);
new = reservation_object_get_excl(src);
dma_fence_get(new);
preempt_disable(); preempt_disable();
write_seqcount_begin(&dst->seq); write_seqcount_begin(&dst->seq);
......
...@@ -71,7 +71,7 @@ config DRM_PARADE_PS8622 ...@@ -71,7 +71,7 @@ config DRM_PARADE_PS8622
config DRM_SIL_SII8620 config DRM_SIL_SII8620
tristate "Silicon Image SII8620 HDMI/MHL bridge" tristate "Silicon Image SII8620 HDMI/MHL bridge"
depends on OF depends on OF && RC_CORE
select DRM_KMS_HELPER select DRM_KMS_HELPER
help help
Silicon Image SII8620 HDMI/MHL bridge chip driver. Silicon Image SII8620 HDMI/MHL bridge chip driver.
...@@ -84,6 +84,14 @@ config DRM_SII902X ...@@ -84,6 +84,14 @@ config DRM_SII902X
---help--- ---help---
Silicon Image sii902x bridge chip driver. Silicon Image sii902x bridge chip driver.
config DRM_SII9234
tristate "Silicon Image SII9234 HDMI/MHL bridge"
depends on OF
---help---
Say Y here if you want support for the MHL interface.
It is an I2C driver, that detects connection of MHL bridge
and starts encapsulation of HDMI signal.
config DRM_TOSHIBA_TC358767 config DRM_TOSHIBA_TC358767
tristate "Toshiba TC358767 eDP bridge" tristate "Toshiba TC358767 eDP bridge"
depends on OF depends on OF
......
...@@ -6,6 +6,7 @@ obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o ...@@ -6,6 +6,7 @@ obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
obj-$(CONFIG_DRM_SII902X) += sii902x.o obj-$(CONFIG_DRM_SII902X) += sii902x.o
obj-$(CONFIG_DRM_SII9234) += sii9234.o
obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o
obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/ obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/
......
...@@ -21,3 +21,11 @@ config DRM_I2C_ADV7533 ...@@ -21,3 +21,11 @@ config DRM_I2C_ADV7533
default y default y
help help
Support for the Analog Devices ADV7533 DSI to HDMI encoder. Support for the Analog Devices ADV7533 DSI to HDMI encoder.
config DRM_I2C_ADV7511_CEC
bool "ADV7511/33 HDMI CEC driver"
depends on DRM_I2C_ADV7511
select CEC_CORE
default y
help
When selected the HDMI transmitter will support the CEC feature.
adv7511-y := adv7511_drv.o adv7511-y := adv7511_drv.o
adv7511-$(CONFIG_DRM_I2C_ADV7511_AUDIO) += adv7511_audio.o adv7511-$(CONFIG_DRM_I2C_ADV7511_AUDIO) += adv7511_audio.o
adv7511-$(CONFIG_DRM_I2C_ADV7511_CEC) += adv7511_cec.o
adv7511-$(CONFIG_DRM_I2C_ADV7533) += adv7533.o adv7511-$(CONFIG_DRM_I2C_ADV7533) += adv7533.o
obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o
...@@ -195,6 +195,25 @@ ...@@ -195,6 +195,25 @@
#define ADV7511_PACKET_GM(x) ADV7511_PACKET(5, x) #define ADV7511_PACKET_GM(x) ADV7511_PACKET(5, x)
#define ADV7511_PACKET_SPARE(x) ADV7511_PACKET(6, x) #define ADV7511_PACKET_SPARE(x) ADV7511_PACKET(6, x)
#define ADV7511_REG_CEC_TX_FRAME_HDR 0x00
#define ADV7511_REG_CEC_TX_FRAME_DATA0 0x01
#define ADV7511_REG_CEC_TX_FRAME_LEN 0x10
#define ADV7511_REG_CEC_TX_ENABLE 0x11
#define ADV7511_REG_CEC_TX_RETRY 0x12
#define ADV7511_REG_CEC_TX_LOW_DRV_CNT 0x14
#define ADV7511_REG_CEC_RX_FRAME_HDR 0x15
#define ADV7511_REG_CEC_RX_FRAME_DATA0 0x16
#define ADV7511_REG_CEC_RX_FRAME_LEN 0x25
#define ADV7511_REG_CEC_RX_ENABLE 0x26
#define ADV7511_REG_CEC_RX_BUFFERS 0x4a
#define ADV7511_REG_CEC_LOG_ADDR_MASK 0x4b
#define ADV7511_REG_CEC_LOG_ADDR_0_1 0x4c
#define ADV7511_REG_CEC_LOG_ADDR_2 0x4d
#define ADV7511_REG_CEC_CLK_DIV 0x4e
#define ADV7511_REG_CEC_SOFT_RESET 0x50
#define ADV7533_REG_CEC_OFFSET 0x70
enum adv7511_input_clock { enum adv7511_input_clock {
ADV7511_INPUT_CLOCK_1X, ADV7511_INPUT_CLOCK_1X,
ADV7511_INPUT_CLOCK_2X, ADV7511_INPUT_CLOCK_2X,
...@@ -297,6 +316,8 @@ enum adv7511_type { ...@@ -297,6 +316,8 @@ enum adv7511_type {
ADV7533, ADV7533,
}; };
#define ADV7511_MAX_ADDRS 3
struct adv7511 { struct adv7511 {
struct i2c_client *i2c_main; struct i2c_client *i2c_main;
struct i2c_client *i2c_edid; struct i2c_client *i2c_edid;
...@@ -341,15 +362,27 @@ struct adv7511 { ...@@ -341,15 +362,27 @@ struct adv7511 {
enum adv7511_type type; enum adv7511_type type;
struct platform_device *audio_pdev; struct platform_device *audio_pdev;
struct cec_adapter *cec_adap;
u8 cec_addr[ADV7511_MAX_ADDRS];
u8 cec_valid_addrs;
bool cec_enabled_adap;
struct clk *cec_clk;
u32 cec_clk_freq;
}; };
#ifdef CONFIG_DRM_I2C_ADV7511_CEC
int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511,
unsigned int offset);
void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1);
#endif
#ifdef CONFIG_DRM_I2C_ADV7533 #ifdef CONFIG_DRM_I2C_ADV7533
void adv7533_dsi_power_on(struct adv7511 *adv); void adv7533_dsi_power_on(struct adv7511 *adv);
void adv7533_dsi_power_off(struct adv7511 *adv); void adv7533_dsi_power_off(struct adv7511 *adv);
void adv7533_mode_set(struct adv7511 *adv, struct drm_display_mode *mode); void adv7533_mode_set(struct adv7511 *adv, struct drm_display_mode *mode);
int adv7533_patch_registers(struct adv7511 *adv); int adv7533_patch_registers(struct adv7511 *adv);
void adv7533_uninit_cec(struct adv7511 *adv); int adv7533_patch_cec_registers(struct adv7511 *adv);
int adv7533_init_cec(struct adv7511 *adv);
int adv7533_attach_dsi(struct adv7511 *adv); int adv7533_attach_dsi(struct adv7511 *adv);
void adv7533_detach_dsi(struct adv7511 *adv); void adv7533_detach_dsi(struct adv7511 *adv);
int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv); int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv);
...@@ -372,11 +405,7 @@ static inline int adv7533_patch_registers(struct adv7511 *adv) ...@@ -372,11 +405,7 @@ static inline int adv7533_patch_registers(struct adv7511 *adv)
return -ENODEV; return -ENODEV;
} }
static inline void adv7533_uninit_cec(struct adv7511 *adv) static inline int adv7533_patch_cec_registers(struct adv7511 *adv)
{
}
static inline int adv7533_init_cec(struct adv7511 *adv)
{ {
return -ENODEV; return -ENODEV;
} }
......
/*
* adv7511_cec.c - Analog Devices ADV7511/33 cec driver
*
* Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <media/cec.h>
#include "adv7511.h"
#define ADV7511_INT1_CEC_MASK \
(ADV7511_INT1_CEC_TX_READY | ADV7511_INT1_CEC_TX_ARBIT_LOST | \
ADV7511_INT1_CEC_TX_RETRY_TIMEOUT | ADV7511_INT1_CEC_RX_READY1)
static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status)
{
unsigned int offset = adv7511->type == ADV7533 ?
ADV7533_REG_CEC_OFFSET : 0;
unsigned int val;
if (regmap_read(adv7511->regmap_cec,
ADV7511_REG_CEC_TX_ENABLE + offset, &val))
return;
if ((val & 0x01) == 0)
return;
if (tx_raw_status & ADV7511_INT1_CEC_TX_ARBIT_LOST) {
cec_transmit_attempt_done(adv7511->cec_adap,
CEC_TX_STATUS_ARB_LOST);
return;
}
if (tx_raw_status & ADV7511_INT1_CEC_TX_RETRY_TIMEOUT) {
u8 status;
u8 err_cnt = 0;
u8 nack_cnt = 0;
u8 low_drive_cnt = 0;
unsigned int cnt;
/*
* We set this status bit since this hardware performs
* retransmissions.
*/
status = CEC_TX_STATUS_MAX_RETRIES;
if (regmap_read(adv7511->regmap_cec,
ADV7511_REG_CEC_TX_LOW_DRV_CNT + offset, &cnt)) {
err_cnt = 1;
status |= CEC_TX_STATUS_ERROR;
} else {
nack_cnt = cnt & 0xf;
if (nack_cnt)
status |= CEC_TX_STATUS_NACK;
low_drive_cnt = cnt >> 4;
if (low_drive_cnt)
status |= CEC_TX_STATUS_LOW_DRIVE;
}
cec_transmit_done(adv7511->cec_adap, status,
0, nack_cnt, low_drive_cnt, err_cnt);
return;
}
if (tx_raw_status & ADV7511_INT1_CEC_TX_READY) {
cec_transmit_attempt_done(adv7511->cec_adap, CEC_TX_STATUS_OK);
return;
}
}
void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1)
{
unsigned int offset = adv7511->type == ADV7533 ?
ADV7533_REG_CEC_OFFSET : 0;
const u32 irq_tx_mask = ADV7511_INT1_CEC_TX_READY |
ADV7511_INT1_CEC_TX_ARBIT_LOST |
ADV7511_INT1_CEC_TX_RETRY_TIMEOUT;
struct cec_msg msg = {};
unsigned int len;
unsigned int val;
u8 i;
if (irq1 & irq_tx_mask)
adv_cec_tx_raw_status(adv7511, irq1);
if (!(irq1 & ADV7511_INT1_CEC_RX_READY1))
return;
if (regmap_read(adv7511->regmap_cec,
ADV7511_REG_CEC_RX_FRAME_LEN + offset, &len))
return;
msg.len = len & 0x1f;
if (msg.len > 16)
msg.len = 16;
if (!msg.len)
return;
for (i = 0; i < msg.len; i++) {
regmap_read(adv7511->regmap_cec,
i + ADV7511_REG_CEC_RX_FRAME_HDR + offset, &val);
msg.msg[i] = val;
}
/* toggle to re-enable rx 1 */
regmap_write(adv7511->regmap_cec,
ADV7511_REG_CEC_RX_BUFFERS + offset, 1);
regmap_write(adv7511->regmap_cec,
ADV7511_REG_CEC_RX_BUFFERS + offset, 0);
cec_received_msg(adv7511->cec_adap, &msg);
}
static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable)
{
struct adv7511 *adv7511 = cec_get_drvdata(adap);
unsigned int offset = adv7511->type == ADV7533 ?
ADV7533_REG_CEC_OFFSET : 0;
if (adv7511->i2c_cec == NULL)
return -EIO;
if (!adv7511->cec_enabled_adap && enable) {
/* power up cec section */
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_CLK_DIV + offset,
0x03, 0x01);
/* legacy mode and clear all rx buffers */
regmap_write(adv7511->regmap_cec,
ADV7511_REG_CEC_RX_BUFFERS + offset, 0x07);
regmap_write(adv7511->regmap_cec,
ADV7511_REG_CEC_RX_BUFFERS + offset, 0);
/* initially disable tx */
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_TX_ENABLE + offset, 1, 0);
/* enabled irqs: */
/* tx: ready */
/* tx: arbitration lost */
/* tx: retry timeout */
/* rx: ready 1 */
regmap_update_bits(adv7511->regmap,
ADV7511_REG_INT_ENABLE(1), 0x3f,
ADV7511_INT1_CEC_MASK);
} else if (adv7511->cec_enabled_adap && !enable) {
regmap_update_bits(adv7511->regmap,
ADV7511_REG_INT_ENABLE(1), 0x3f, 0);
/* disable address mask 1-3 */
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
0x70, 0x00);
/* power down cec section */
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_CLK_DIV + offset,
0x03, 0x00);
adv7511->cec_valid_addrs = 0;
}
adv7511->cec_enabled_adap = enable;
return 0;
}
static int adv7511_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
{
struct adv7511 *adv7511 = cec_get_drvdata(adap);
unsigned int offset = adv7511->type == ADV7533 ?
ADV7533_REG_CEC_OFFSET : 0;
unsigned int i, free_idx = ADV7511_MAX_ADDRS;
if (!adv7511->cec_enabled_adap)
return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO;
if (addr == CEC_LOG_ADDR_INVALID) {
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
0x70, 0);
adv7511->cec_valid_addrs = 0;
return 0;
}
for (i = 0; i < ADV7511_MAX_ADDRS; i++) {
bool is_valid = adv7511->cec_valid_addrs & (1 << i);
if (free_idx == ADV7511_MAX_ADDRS && !is_valid)
free_idx = i;
if (is_valid && adv7511->cec_addr[i] == addr)
return 0;
}
if (i == ADV7511_MAX_ADDRS) {
i = free_idx;
if (i == ADV7511_MAX_ADDRS)
return -ENXIO;
}
adv7511->cec_addr[i] = addr;
adv7511->cec_valid_addrs |= 1 << i;
switch (i) {
case 0:
/* enable address mask 0 */
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
0x10, 0x10);
/* set address for mask 0 */
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_LOG_ADDR_0_1 + offset,
0x0f, addr);
break;
case 1:
/* enable address mask 1 */
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
0x20, 0x20);
/* set address for mask 1 */
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_LOG_ADDR_0_1 + offset,
0xf0, addr << 4);
break;
case 2:
/* enable address mask 2 */
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
0x40, 0x40);
/* set address for mask 1 */
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_LOG_ADDR_2 + offset,
0x0f, addr);
break;
}
return 0;
}
static int adv7511_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
u32 signal_free_time, struct cec_msg *msg)
{
struct adv7511 *adv7511 = cec_get_drvdata(adap);
unsigned int offset = adv7511->type == ADV7533 ?
ADV7533_REG_CEC_OFFSET : 0;
u8 len = msg->len;
unsigned int i;
/*
* The number of retries is the number of attempts - 1, but retry
* at least once. It's not clear if a value of 0 is allowed, so
* let's do at least one retry.
*/
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_TX_RETRY + offset,
0x70, max(1, attempts - 1) << 4);
/* blocking, clear cec tx irq status */
regmap_update_bits(adv7511->regmap, ADV7511_REG_INT(1), 0x38, 0x38);
/* write data */
for (i = 0; i < len; i++)
regmap_write(adv7511->regmap_cec,
i + ADV7511_REG_CEC_TX_FRAME_HDR + offset,
msg->msg[i]);
/* set length (data + header) */
regmap_write(adv7511->regmap_cec,
ADV7511_REG_CEC_TX_FRAME_LEN + offset, len);
/* start transmit, enable tx */
regmap_write(adv7511->regmap_cec,
ADV7511_REG_CEC_TX_ENABLE + offset, 0x01);
return 0;
}
static const struct cec_adap_ops adv7511_cec_adap_ops = {
.adap_enable = adv7511_cec_adap_enable,
.adap_log_addr = adv7511_cec_adap_log_addr,
.adap_transmit = adv7511_cec_adap_transmit,
};
static int adv7511_cec_parse_dt(struct device *dev, struct adv7511 *adv7511)
{
adv7511->cec_clk = devm_clk_get(dev, "cec");
if (IS_ERR(adv7511->cec_clk)) {
int ret = PTR_ERR(adv7511->cec_clk);
adv7511->cec_clk = NULL;
return ret;
}
clk_prepare_enable(adv7511->cec_clk);
adv7511->cec_clk_freq = clk_get_rate(adv7511->cec_clk);
return 0;
}
int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511,
unsigned int offset)
{
int ret = adv7511_cec_parse_dt(dev, adv7511);
if (ret)
return ret;
adv7511->cec_adap = cec_allocate_adapter(&adv7511_cec_adap_ops,
adv7511, dev_name(dev), CEC_CAP_DEFAULTS, ADV7511_MAX_ADDRS);
if (IS_ERR(adv7511->cec_adap))
return PTR_ERR(adv7511->cec_adap);
regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL + offset, 0);
/* cec soft reset */
regmap_write(adv7511->regmap_cec,
ADV7511_REG_CEC_SOFT_RESET + offset, 0x01);
regmap_write(adv7511->regmap_cec,
ADV7511_REG_CEC_SOFT_RESET + offset, 0x00);
/* legacy mode */
regmap_write(adv7511->regmap_cec,
ADV7511_REG_CEC_RX_BUFFERS + offset, 0x00);
regmap_write(adv7511->regmap_cec,
ADV7511_REG_CEC_CLK_DIV + offset,
((adv7511->cec_clk_freq / 750000) - 1) << 2);
ret = cec_register_adapter(adv7511->cec_adap, dev);
if (ret) {
cec_delete_adapter(adv7511->cec_adap);
adv7511->cec_adap = NULL;
}
return ret;
}
...@@ -11,12 +11,15 @@ ...@@ -11,12 +11,15 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/of_device.h> #include <linux/of_device.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/clk.h>
#include <drm/drmP.h> #include <drm/drmP.h>
#include <drm/drm_atomic.h> #include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h> #include <drm/drm_atomic_helper.h>
#include <drm/drm_edid.h> #include <drm/drm_edid.h>
#include <media/cec.h>
#include "adv7511.h" #include "adv7511.h"
/* ADI recommended values for proper operation. */ /* ADI recommended values for proper operation. */
...@@ -336,8 +339,10 @@ static void __adv7511_power_on(struct adv7511 *adv7511) ...@@ -336,8 +339,10 @@ static void __adv7511_power_on(struct adv7511 *adv7511)
*/ */
regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(0), regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(0),
ADV7511_INT0_EDID_READY | ADV7511_INT0_HPD); ADV7511_INT0_EDID_READY | ADV7511_INT0_HPD);
regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(1), regmap_update_bits(adv7511->regmap,
ADV7511_INT1_DDC_ERROR); ADV7511_REG_INT_ENABLE(1),
ADV7511_INT1_DDC_ERROR,
ADV7511_INT1_DDC_ERROR);
} }
/* /*
...@@ -373,6 +378,9 @@ static void __adv7511_power_off(struct adv7511 *adv7511) ...@@ -373,6 +378,9 @@ static void __adv7511_power_off(struct adv7511 *adv7511)
regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER,
ADV7511_POWER_POWER_DOWN, ADV7511_POWER_POWER_DOWN,
ADV7511_POWER_POWER_DOWN); ADV7511_POWER_POWER_DOWN);
regmap_update_bits(adv7511->regmap,
ADV7511_REG_INT_ENABLE(1),
ADV7511_INT1_DDC_ERROR, 0);
regcache_mark_dirty(adv7511->regmap); regcache_mark_dirty(adv7511->regmap);
} }
...@@ -423,6 +431,8 @@ static void adv7511_hpd_work(struct work_struct *work) ...@@ -423,6 +431,8 @@ static void adv7511_hpd_work(struct work_struct *work)
if (adv7511->connector.status != status) { if (adv7511->connector.status != status) {
adv7511->connector.status = status; adv7511->connector.status = status;
if (status == connector_status_disconnected)
cec_phys_addr_invalidate(adv7511->cec_adap);
drm_kms_helper_hotplug_event(adv7511->connector.dev); drm_kms_helper_hotplug_event(adv7511->connector.dev);
} }
} }
...@@ -453,6 +463,10 @@ static int adv7511_irq_process(struct adv7511 *adv7511, bool process_hpd) ...@@ -453,6 +463,10 @@ static int adv7511_irq_process(struct adv7511 *adv7511, bool process_hpd)
wake_up_all(&adv7511->wq); wake_up_all(&adv7511->wq);
} }
#ifdef CONFIG_DRM_I2C_ADV7511_CEC
adv7511_cec_irq_process(adv7511, irq1);
#endif
return 0; return 0;
} }
...@@ -595,6 +609,8 @@ static int adv7511_get_modes(struct adv7511 *adv7511, ...@@ -595,6 +609,8 @@ static int adv7511_get_modes(struct adv7511 *adv7511,
kfree(edid); kfree(edid);
cec_s_phys_addr_from_edid(adv7511->cec_adap, edid);
return count; return count;
} }
...@@ -919,6 +935,65 @@ static void adv7511_uninit_regulators(struct adv7511 *adv) ...@@ -919,6 +935,65 @@ static void adv7511_uninit_regulators(struct adv7511 *adv)
regulator_bulk_disable(adv->num_supplies, adv->supplies); regulator_bulk_disable(adv->num_supplies, adv->supplies);
} }
static bool adv7511_cec_register_volatile(struct device *dev, unsigned int reg)
{
struct i2c_client *i2c = to_i2c_client(dev);
struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
if (adv7511->type == ADV7533)
reg -= ADV7533_REG_CEC_OFFSET;
switch (reg) {
case ADV7511_REG_CEC_RX_FRAME_HDR:
case ADV7511_REG_CEC_RX_FRAME_DATA0...
ADV7511_REG_CEC_RX_FRAME_DATA0 + 14:
case ADV7511_REG_CEC_RX_FRAME_LEN:
case ADV7511_REG_CEC_RX_BUFFERS:
case ADV7511_REG_CEC_TX_LOW_DRV_CNT:
return true;
}
return false;
}
static const struct regmap_config adv7511_cec_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0xff,
.cache_type = REGCACHE_RBTREE,
.volatile_reg = adv7511_cec_register_volatile,
};
static int adv7511_init_cec_regmap(struct adv7511 *adv)
{
int ret;
adv->i2c_cec = i2c_new_dummy(adv->i2c_main->adapter,
adv->i2c_main->addr - 1);
if (!adv->i2c_cec)
return -ENOMEM;
i2c_set_clientdata(adv->i2c_cec, adv);
adv->regmap_cec = devm_regmap_init_i2c(adv->i2c_cec,
&adv7511_cec_regmap_config);
if (IS_ERR(adv->regmap_cec)) {
ret = PTR_ERR(adv->regmap_cec);
goto err;
}
if (adv->type == ADV7533) {
ret = adv7533_patch_cec_registers(adv);
if (ret)
goto err;
}
return 0;
err:
i2c_unregister_device(adv->i2c_cec);
return ret;
}
static int adv7511_parse_dt(struct device_node *np, static int adv7511_parse_dt(struct device_node *np,
struct adv7511_link_config *config) struct adv7511_link_config *config)
{ {
...@@ -1009,6 +1084,7 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) ...@@ -1009,6 +1084,7 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
struct device *dev = &i2c->dev; struct device *dev = &i2c->dev;
unsigned int main_i2c_addr = i2c->addr << 1; unsigned int main_i2c_addr = i2c->addr << 1;
unsigned int edid_i2c_addr = main_i2c_addr + 4; unsigned int edid_i2c_addr = main_i2c_addr + 4;
unsigned int offset;
unsigned int val; unsigned int val;
int ret; int ret;
...@@ -1092,11 +1168,9 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) ...@@ -1092,11 +1168,9 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
goto uninit_regulators; goto uninit_regulators;
} }
if (adv7511->type == ADV7533) { ret = adv7511_init_cec_regmap(adv7511);
ret = adv7533_init_cec(adv7511); if (ret)
if (ret) goto err_i2c_unregister_edid;
goto err_i2c_unregister_edid;
}
INIT_WORK(&adv7511->hpd_work, adv7511_hpd_work); INIT_WORK(&adv7511->hpd_work, adv7511_hpd_work);
...@@ -1111,10 +1185,6 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) ...@@ -1111,10 +1185,6 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
goto err_unregister_cec; goto err_unregister_cec;
} }
/* CEC is unused for now */
regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL,
ADV7511_CEC_CTRL_POWER_DOWN);
adv7511_power_off(adv7511); adv7511_power_off(adv7511);
i2c_set_clientdata(i2c, adv7511); i2c_set_clientdata(i2c, adv7511);
...@@ -1129,10 +1199,23 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) ...@@ -1129,10 +1199,23 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
adv7511_audio_init(dev, adv7511); adv7511_audio_init(dev, adv7511);
offset = adv7511->type == ADV7533 ? ADV7533_REG_CEC_OFFSET : 0;
#ifdef CONFIG_DRM_I2C_ADV7511_CEC
ret = adv7511_cec_init(dev, adv7511, offset);
if (ret)
goto err_unregister_cec;
#else
regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL + offset,
ADV7511_CEC_CTRL_POWER_DOWN);
#endif
return 0; return 0;
err_unregister_cec: err_unregister_cec:
adv7533_uninit_cec(adv7511); i2c_unregister_device(adv7511->i2c_cec);
if (adv7511->cec_clk)
clk_disable_unprepare(adv7511->cec_clk);
err_i2c_unregister_edid: err_i2c_unregister_edid:
i2c_unregister_device(adv7511->i2c_edid); i2c_unregister_device(adv7511->i2c_edid);
uninit_regulators: uninit_regulators:
...@@ -1145,10 +1228,11 @@ static int adv7511_remove(struct i2c_client *i2c) ...@@ -1145,10 +1228,11 @@ static int adv7511_remove(struct i2c_client *i2c)
{ {
struct adv7511 *adv7511 = i2c_get_clientdata(i2c); struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
if (adv7511->type == ADV7533) { if (adv7511->type == ADV7533)
adv7533_detach_dsi(adv7511); adv7533_detach_dsi(adv7511);
adv7533_uninit_cec(adv7511); i2c_unregister_device(adv7511->i2c_cec);
} if (adv7511->cec_clk)
clk_disable_unprepare(adv7511->cec_clk);
adv7511_uninit_regulators(adv7511); adv7511_uninit_regulators(adv7511);
...@@ -1156,6 +1240,8 @@ static int adv7511_remove(struct i2c_client *i2c) ...@@ -1156,6 +1240,8 @@ static int adv7511_remove(struct i2c_client *i2c)
adv7511_audio_exit(adv7511); adv7511_audio_exit(adv7511);
cec_unregister_adapter(adv7511->cec_adap);
i2c_unregister_device(adv7511->i2c_edid); i2c_unregister_device(adv7511->i2c_edid);
return 0; return 0;
......
...@@ -32,14 +32,6 @@ static const struct reg_sequence adv7533_cec_fixed_registers[] = { ...@@ -32,14 +32,6 @@ static const struct reg_sequence adv7533_cec_fixed_registers[] = {
{ 0x05, 0xc8 }, { 0x05, 0xc8 },
}; };
static const struct regmap_config adv7533_cec_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0xff,
.cache_type = REGCACHE_RBTREE,
};
static void adv7511_dsi_config_timing_gen(struct adv7511 *adv) static void adv7511_dsi_config_timing_gen(struct adv7511 *adv)
{ {
struct mipi_dsi_device *dsi = adv->dsi; struct mipi_dsi_device *dsi = adv->dsi;
...@@ -145,37 +137,11 @@ int adv7533_patch_registers(struct adv7511 *adv) ...@@ -145,37 +137,11 @@ int adv7533_patch_registers(struct adv7511 *adv)
ARRAY_SIZE(adv7533_fixed_registers)); ARRAY_SIZE(adv7533_fixed_registers));
} }
void adv7533_uninit_cec(struct adv7511 *adv) int adv7533_patch_cec_registers(struct adv7511 *adv)
{
i2c_unregister_device(adv->i2c_cec);
}
int adv7533_init_cec(struct adv7511 *adv)
{ {
int ret; return regmap_register_patch(adv->regmap_cec,
adv->i2c_cec = i2c_new_dummy(adv->i2c_main->adapter,
adv->i2c_main->addr - 1);
if (!adv->i2c_cec)
return -ENOMEM;
adv->regmap_cec = devm_regmap_init_i2c(adv->i2c_cec,
&adv7533_cec_regmap_config);
if (IS_ERR(adv->regmap_cec)) {
ret = PTR_ERR(adv->regmap_cec);
goto err;
}
ret = regmap_register_patch(adv->regmap_cec,
adv7533_cec_fixed_registers, adv7533_cec_fixed_registers,
ARRAY_SIZE(adv7533_cec_fixed_registers)); ARRAY_SIZE(adv7533_cec_fixed_registers));
if (ret)
goto err;
return 0;
err:
adv7533_uninit_cec(adv);
return ret;
} }
int adv7533_attach_dsi(struct adv7511 *adv) int adv7533_attach_dsi(struct adv7511 *adv)
......
...@@ -188,7 +188,15 @@ EXPORT_SYMBOL(drm_panel_bridge_add); ...@@ -188,7 +188,15 @@ EXPORT_SYMBOL(drm_panel_bridge_add);
*/ */
void drm_panel_bridge_remove(struct drm_bridge *bridge) void drm_panel_bridge_remove(struct drm_bridge *bridge)
{ {
struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge); struct panel_bridge *panel_bridge;
if (!bridge)
return;
if (bridge->funcs != &panel_bridge_bridge_funcs)
return;
panel_bridge = drm_bridge_to_panel_bridge(bridge);
drm_bridge_remove(bridge); drm_bridge_remove(bridge);
devm_kfree(panel_bridge->panel->dev, bridge); devm_kfree(panel_bridge->panel->dev, bridge);
......
/*
* Copyright (C) 2017 Samsung Electronics
*
* Authors:
* Tomasz Stanislawski <t.stanislaws@samsung.com>
* Maciej Purski <m.purski@samsung.com>
*
* Based on sii9234 driver created by:
* Adam Hampson <ahampson@sta.samsung.com>
* Erik Gilling <konkers@android.com>
* Shankar Bandal <shankar.b@samsung.com>
* Dharam Kumar <dharam.kr@samsung.com>
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program
*
*/
#include <drm/bridge/mhl.h>
#include <drm/drm_crtc.h>
#include <drm/drm_edid.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#define CBUS_DEVCAP_OFFSET 0x80
#define SII9234_MHL_VERSION 0x11
#define SII9234_SCRATCHPAD_SIZE 0x10
#define SII9234_INT_STAT_SIZE 0x33
#define BIT_TMDS_CCTRL_TMDS_OE BIT(4)
#define MHL_HPD_OUT_OVR_EN BIT(4)
#define MHL_HPD_OUT_OVR_VAL BIT(5)
#define MHL_INIT_TIMEOUT 0x0C
/* MHL Tx registers and bits */
#define MHL_TX_SRST 0x05
#define MHL_TX_SYSSTAT_REG 0x09
#define MHL_TX_INTR1_REG 0x71
#define MHL_TX_INTR4_REG 0x74
#define MHL_TX_INTR1_ENABLE_REG 0x75
#define MHL_TX_INTR4_ENABLE_REG 0x78
#define MHL_TX_INT_CTRL_REG 0x79
#define MHL_TX_TMDS_CCTRL 0x80
#define MHL_TX_DISC_CTRL1_REG 0x90
#define MHL_TX_DISC_CTRL2_REG 0x91
#define MHL_TX_DISC_CTRL3_REG 0x92
#define MHL_TX_DISC_CTRL4_REG 0x93
#define MHL_TX_DISC_CTRL5_REG 0x94
#define MHL_TX_DISC_CTRL6_REG 0x95
#define MHL_TX_DISC_CTRL7_REG 0x96
#define MHL_TX_DISC_CTRL8_REG 0x97
#define MHL_TX_STAT2_REG 0x99
#define MHL_TX_MHLTX_CTL1_REG 0xA0
#define MHL_TX_MHLTX_CTL2_REG 0xA1
#define MHL_TX_MHLTX_CTL4_REG 0xA3
#define MHL_TX_MHLTX_CTL6_REG 0xA5
#define MHL_TX_MHLTX_CTL7_REG 0xA6
#define RSEN_STATUS BIT(2)
#define HPD_CHANGE_INT BIT(6)
#define RSEN_CHANGE_INT BIT(5)
#define RGND_READY_INT BIT(6)
#define VBUS_LOW_INT BIT(5)
#define CBUS_LKOUT_INT BIT(4)
#define MHL_DISC_FAIL_INT BIT(3)
#define MHL_EST_INT BIT(2)
#define HPD_CHANGE_INT_MASK BIT(6)
#define RSEN_CHANGE_INT_MASK BIT(5)
#define RGND_READY_MASK BIT(6)
#define CBUS_LKOUT_MASK BIT(4)
#define MHL_DISC_FAIL_MASK BIT(3)
#define MHL_EST_MASK BIT(2)
#define SKIP_GND BIT(6)
#define ATT_THRESH_SHIFT 0x04
#define ATT_THRESH_MASK (0x03 << ATT_THRESH_SHIFT)
#define USB_D_OEN BIT(3)
#define DEGLITCH_TIME_MASK 0x07
#define DEGLITCH_TIME_2MS 0
#define DEGLITCH_TIME_4MS 1
#define DEGLITCH_TIME_8MS 2
#define DEGLITCH_TIME_16MS 3
#define DEGLITCH_TIME_40MS 4
#define DEGLITCH_TIME_50MS 5
#define DEGLITCH_TIME_60MS 6
#define DEGLITCH_TIME_128MS 7
#define USB_D_OVR BIT(7)
#define USB_ID_OVR BIT(6)
#define DVRFLT_SEL BIT(5)
#define BLOCK_RGND_INT BIT(4)
#define SKIP_DEG BIT(3)
#define CI2CA_POL BIT(2)
#define CI2CA_WKUP BIT(1)
#define SINGLE_ATT BIT(0)
#define USB_D_ODN BIT(5)
#define VBUS_CHECK BIT(2)
#define RGND_INTP_MASK 0x03
#define RGND_INTP_OPEN 0
#define RGND_INTP_2K 1
#define RGND_INTP_1K 2
#define RGND_INTP_SHORT 3
/* HDMI registers */
#define HDMI_RX_TMDS0_CCTRL1_REG 0x10
#define HDMI_RX_TMDS_CLK_EN_REG 0x11
#define HDMI_RX_TMDS_CH_EN_REG 0x12
#define HDMI_RX_PLL_CALREFSEL_REG 0x17
#define HDMI_RX_PLL_VCOCAL_REG 0x1A
#define HDMI_RX_EQ_DATA0_REG 0x22
#define HDMI_RX_EQ_DATA1_REG 0x23
#define HDMI_RX_EQ_DATA2_REG 0x24
#define HDMI_RX_EQ_DATA3_REG 0x25
#define HDMI_RX_EQ_DATA4_REG 0x26
#define HDMI_RX_TMDS_ZONE_CTRL_REG 0x4C
#define HDMI_RX_TMDS_MODE_CTRL_REG 0x4D
/* CBUS registers */
#define CBUS_INT_STATUS_1_REG 0x08
#define CBUS_INTR1_ENABLE_REG 0x09
#define CBUS_MSC_REQ_ABORT_REASON_REG 0x0D
#define CBUS_INT_STATUS_2_REG 0x1E
#define CBUS_INTR2_ENABLE_REG 0x1F
#define CBUS_LINK_CONTROL_2_REG 0x31
#define CBUS_MHL_STATUS_REG_0 0xB0
#define CBUS_MHL_STATUS_REG_1 0xB1
#define BIT_CBUS_RESET BIT(3)
#define SET_HPD_DOWNSTREAM BIT(6)
/* TPI registers */
#define TPI_DPD_REG 0x3D
/* Timeouts in msec */
#define T_SRC_VBUS_CBUS_TO_STABLE 200
#define T_SRC_CBUS_FLOAT 100
#define T_SRC_CBUS_DEGLITCH 2
#define T_SRC_RXSENSE_DEGLITCH 110
#define MHL1_MAX_CLK 75000 /* in kHz */
#define I2C_TPI_ADDR 0x3D
#define I2C_HDMI_ADDR 0x49
#define I2C_CBUS_ADDR 0x64
enum sii9234_state {
ST_OFF,
ST_D3,
ST_RGND_INIT,
ST_RGND_1K,
ST_RSEN_HIGH,
ST_MHL_ESTABLISHED,
ST_FAILURE_DISCOVERY,
ST_FAILURE,
};
struct sii9234 {
struct i2c_client *client[4];
struct drm_bridge bridge;
struct device *dev;
struct gpio_desc *gpio_reset;
int i2c_error;
struct regulator_bulk_data supplies[4];
struct mutex lock; /* Protects fields below and device registers */
enum sii9234_state state;
};
enum sii9234_client_id {
I2C_MHL,
I2C_TPI,
I2C_HDMI,
I2C_CBUS,
};
static const char * const sii9234_client_name[] = {
[I2C_MHL] = "MHL",
[I2C_TPI] = "TPI",
[I2C_HDMI] = "HDMI",
[I2C_CBUS] = "CBUS",
};
static int sii9234_writeb(struct sii9234 *ctx, int id, int offset,
int value)
{
int ret;
struct i2c_client *client = ctx->client[id];
if (ctx->i2c_error)
return ctx->i2c_error;
ret = i2c_smbus_write_byte_data(client, offset, value);
if (ret < 0)
dev_err(ctx->dev, "writeb: %4s[0x%02x] <- 0x%02x\n",
sii9234_client_name[id], offset, value);
ctx->i2c_error = ret;
return ret;
}
static int sii9234_writebm(struct sii9234 *ctx, int id, int offset,
int value, int mask)
{
int ret;
struct i2c_client *client = ctx->client[id];
if (ctx->i2c_error)
return ctx->i2c_error;
ret = i2c_smbus_write_byte(client, offset);
if (ret < 0) {
dev_err(ctx->dev, "writebm: %4s[0x%02x] <- 0x%02x\n",
sii9234_client_name[id], offset, value);
ctx->i2c_error = ret;
return ret;
}
ret = i2c_smbus_read_byte(client);
if (ret < 0) {
dev_err(ctx->dev, "writebm: %4s[0x%02x] <- 0x%02x\n",
sii9234_client_name[id], offset, value);
ctx->i2c_error = ret;
return ret;
}
value = (value & mask) | (ret & ~mask);
ret = i2c_smbus_write_byte_data(client, offset, value);
if (ret < 0) {
dev_err(ctx->dev, "writebm: %4s[0x%02x] <- 0x%02x\n",
sii9234_client_name[id], offset, value);
ctx->i2c_error = ret;
}
return ret;
}
static int sii9234_readb(struct sii9234 *ctx, int id, int offset)
{
int ret;
struct i2c_client *client = ctx->client[id];
if (ctx->i2c_error)
return ctx->i2c_error;
ret = i2c_smbus_write_byte(client, offset);
if (ret < 0) {
dev_err(ctx->dev, "readb: %4s[0x%02x]\n",
sii9234_client_name[id], offset);
ctx->i2c_error = ret;
return ret;
}
ret = i2c_smbus_read_byte(client);
if (ret < 0) {
dev_err(ctx->dev, "readb: %4s[0x%02x]\n",
sii9234_client_name[id], offset);
ctx->i2c_error = ret;
}
return ret;
}
static int sii9234_clear_error(struct sii9234 *ctx)
{
int ret = ctx->i2c_error;
ctx->i2c_error = 0;
return ret;
}
#define mhl_tx_writeb(sii9234, offset, value) \
sii9234_writeb(sii9234, I2C_MHL, offset, value)
#define mhl_tx_writebm(sii9234, offset, value, mask) \
sii9234_writebm(sii9234, I2C_MHL, offset, value, mask)
#define mhl_tx_readb(sii9234, offset) \
sii9234_readb(sii9234, I2C_MHL, offset)
#define cbus_writeb(sii9234, offset, value) \
sii9234_writeb(sii9234, I2C_CBUS, offset, value)
#define cbus_writebm(sii9234, offset, value, mask) \
sii9234_writebm(sii9234, I2C_CBUS, offset, value, mask)
#define cbus_readb(sii9234, offset) \
sii9234_readb(sii9234, I2C_CBUS, offset)
#define hdmi_writeb(sii9234, offset, value) \
sii9234_writeb(sii9234, I2C_HDMI, offset, value)
#define hdmi_writebm(sii9234, offset, value, mask) \
sii9234_writebm(sii9234, I2C_HDMI, offset, value, mask)
#define hdmi_readb(sii9234, offset) \
sii9234_readb(sii9234, I2C_HDMI, offset)
#define tpi_writeb(sii9234, offset, value) \
sii9234_writeb(sii9234, I2C_TPI, offset, value)
#define tpi_writebm(sii9234, offset, value, mask) \
sii9234_writebm(sii9234, I2C_TPI, offset, value, mask)
#define tpi_readb(sii9234, offset) \
sii9234_readb(sii9234, I2C_TPI, offset)
static u8 sii9234_tmds_control(struct sii9234 *ctx, bool enable)
{
mhl_tx_writebm(ctx, MHL_TX_TMDS_CCTRL, enable ? ~0 : 0,
BIT_TMDS_CCTRL_TMDS_OE);
mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, enable ? ~0 : 0,
MHL_HPD_OUT_OVR_EN | MHL_HPD_OUT_OVR_VAL);
return sii9234_clear_error(ctx);
}
static int sii9234_cbus_reset(struct sii9234 *ctx)
{
int i;
mhl_tx_writebm(ctx, MHL_TX_SRST, ~0, BIT_CBUS_RESET);
msleep(T_SRC_CBUS_DEGLITCH);
mhl_tx_writebm(ctx, MHL_TX_SRST, 0, BIT_CBUS_RESET);
for (i = 0; i < 4; i++) {
/*
* Enable WRITE_STAT interrupt for writes to all
* 4 MSC Status registers.
*/
cbus_writeb(ctx, 0xE0 + i, 0xF2);
/*
* Enable SET_INT interrupt for writes to all
* 4 MSC Interrupt registers.
*/
cbus_writeb(ctx, 0xF0 + i, 0xF2);
}
return sii9234_clear_error(ctx);
}
/* Require to chek mhl imformation of samsung in cbus_init_register */
static int sii9234_cbus_init(struct sii9234 *ctx)
{
cbus_writeb(ctx, 0x07, 0xF2);
cbus_writeb(ctx, 0x40, 0x03);
cbus_writeb(ctx, 0x42, 0x06);
cbus_writeb(ctx, 0x36, 0x0C);
cbus_writeb(ctx, 0x3D, 0xFD);
cbus_writeb(ctx, 0x1C, 0x01);
cbus_writeb(ctx, 0x1D, 0x0F);
cbus_writeb(ctx, 0x44, 0x02);
/* Setup our devcap */
cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_DEV_STATE, 0x00);
cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_MHL_VERSION,
SII9234_MHL_VERSION);
cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_CAT,
MHL_DCAP_CAT_SOURCE);
cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_ADOPTER_ID_H, 0x01);
cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_ADOPTER_ID_L, 0x41);
cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_VID_LINK_MODE,
MHL_DCAP_VID_LINK_RGB444 | MHL_DCAP_VID_LINK_YCBCR444);
cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_VIDEO_TYPE,
MHL_DCAP_VT_GRAPHICS);
cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_LOG_DEV_MAP,
MHL_DCAP_LD_GUI);
cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_BANDWIDTH, 0x0F);
cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_FEATURE_FLAG,
MHL_DCAP_FEATURE_RCP_SUPPORT | MHL_DCAP_FEATURE_RAP_SUPPORT
| MHL_DCAP_FEATURE_SP_SUPPORT);
cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_DEVICE_ID_H, 0x0);
cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_DEVICE_ID_L, 0x0);
cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_SCRATCHPAD_SIZE,
SII9234_SCRATCHPAD_SIZE);
cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_INT_STAT_SIZE,
SII9234_INT_STAT_SIZE);
cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_RESERVED, 0);
cbus_writebm(ctx, 0x31, 0x0C, 0x0C);
cbus_writeb(ctx, 0x30, 0x01);
cbus_writebm(ctx, 0x3C, 0x30, 0x38);
cbus_writebm(ctx, 0x22, 0x0D, 0x0F);
cbus_writebm(ctx, 0x2E, 0x15, 0x15);
cbus_writeb(ctx, CBUS_INTR1_ENABLE_REG, 0);
cbus_writeb(ctx, CBUS_INTR2_ENABLE_REG, 0);
return sii9234_clear_error(ctx);
}
static void force_usb_id_switch_open(struct sii9234 *ctx)
{
/* Disable CBUS discovery */
mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL1_REG, 0, 0x01);
/* Force USB ID switch to open */
mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, ~0, USB_ID_OVR);
mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL3_REG, ~0, 0x86);
/* Force upstream HPD to 0 when not in MHL mode. */
mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, 0, 0x30);
}
static void release_usb_id_switch_open(struct sii9234 *ctx)
{
msleep(T_SRC_CBUS_FLOAT);
/* Clear USB ID switch to open */
mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, 0, USB_ID_OVR);
/* Enable CBUS discovery */
mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL1_REG, ~0, 0x01);
}
static int sii9234_power_init(struct sii9234 *ctx)
{
/* Force the SiI9234 into the D0 state. */
tpi_writeb(ctx, TPI_DPD_REG, 0x3F);
/* Enable TxPLL Clock */
hdmi_writeb(ctx, HDMI_RX_TMDS_CLK_EN_REG, 0x01);
/* Enable Tx Clock Path & Equalizer */
hdmi_writeb(ctx, HDMI_RX_TMDS_CH_EN_REG, 0x15);
/* Power Up TMDS */
mhl_tx_writeb(ctx, 0x08, 0x35);
return sii9234_clear_error(ctx);
}
static int sii9234_hdmi_init(struct sii9234 *ctx)
{
hdmi_writeb(ctx, HDMI_RX_TMDS0_CCTRL1_REG, 0xC1);
hdmi_writeb(ctx, HDMI_RX_PLL_CALREFSEL_REG, 0x03);
hdmi_writeb(ctx, HDMI_RX_PLL_VCOCAL_REG, 0x20);
hdmi_writeb(ctx, HDMI_RX_EQ_DATA0_REG, 0x8A);
hdmi_writeb(ctx, HDMI_RX_EQ_DATA1_REG, 0x6A);
hdmi_writeb(ctx, HDMI_RX_EQ_DATA2_REG, 0xAA);
hdmi_writeb(ctx, HDMI_RX_EQ_DATA3_REG, 0xCA);
hdmi_writeb(ctx, HDMI_RX_EQ_DATA4_REG, 0xEA);
hdmi_writeb(ctx, HDMI_RX_TMDS_ZONE_CTRL_REG, 0xA0);
hdmi_writeb(ctx, HDMI_RX_TMDS_MODE_CTRL_REG, 0x00);
mhl_tx_writeb(ctx, MHL_TX_TMDS_CCTRL, 0x34);
hdmi_writeb(ctx, 0x45, 0x44);
hdmi_writeb(ctx, 0x31, 0x0A);
hdmi_writeb(ctx, HDMI_RX_TMDS0_CCTRL1_REG, 0xC1);
return sii9234_clear_error(ctx);
}
static int sii9234_mhl_tx_ctl_int(struct sii9234 *ctx)
{
mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL1_REG, 0xD0);
mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL2_REG, 0xFC);
mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL4_REG, 0xEB);
mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL7_REG, 0x0C);
return sii9234_clear_error(ctx);
}
static int sii9234_reset(struct sii9234 *ctx)
{
int ret;
sii9234_clear_error(ctx);
ret = sii9234_power_init(ctx);
if (ret < 0)
return ret;
ret = sii9234_cbus_reset(ctx);
if (ret < 0)
return ret;
ret = sii9234_hdmi_init(ctx);
if (ret < 0)
return ret;
ret = sii9234_mhl_tx_ctl_int(ctx);
if (ret < 0)
return ret;
/* Enable HDCP Compliance safety */
mhl_tx_writeb(ctx, 0x2B, 0x01);
/* CBUS discovery cycle time for each drive and float = 150us */
mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL1_REG, 0x04, 0x06);
/* Clear bit 6 (reg_skip_rgnd) */
mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL2_REG, (1 << 7) /* Reserved */
| 2 << ATT_THRESH_SHIFT | DEGLITCH_TIME_50MS);
/*
* Changed from 66 to 65 for 94[1:0] = 01 = 5k reg_cbusmhl_pup_sel
* 1.8V CBUS VTH & GND threshold
* to meet CTS 3.3.7.2 spec
*/
mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL5_REG, 0x77);
cbus_writebm(ctx, CBUS_LINK_CONTROL_2_REG, ~0, MHL_INIT_TIMEOUT);
mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL6_REG, 0xA0);
/* RGND & single discovery attempt (RGND blocking) */
mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL6_REG, BLOCK_RGND_INT |
DVRFLT_SEL | SINGLE_ATT);
/* Use VBUS path of discovery state machine */
mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL8_REG, 0);
/* 0x92[3] sets the CBUS / ID switch */
mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, ~0, USB_ID_OVR);
/*
* To allow RGND engine to operate correctly.
* When moving the chip from D2 to D0 (power up, init regs)
* the values should be
* 94[1:0] = 01 reg_cbusmhl_pup_sel[1:0] should be set for 5k
* 93[7:6] = 10 reg_cbusdisc_pup_sel[1:0] should be
* set for 10k (default)
* 93[5:4] = 00 reg_cbusidle_pup_sel[1:0] = open (default)
*/
mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL3_REG, ~0, 0x86);
/*
* Change from CC to 8C to match 5K
* to meet CTS 3.3.72 spec
*/
mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL4_REG, ~0, 0x8C);
/* Configure the interrupt as active high */
mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, 0, 0x06);
msleep(25);
/* Release usb_id switch */
mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, 0, USB_ID_OVR);
mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL1_REG, 0x27);
ret = sii9234_clear_error(ctx);
if (ret < 0)
return ret;
ret = sii9234_cbus_init(ctx);
if (ret < 0)
return ret;
/* Enable Auto soft reset on SCDT = 0 */
mhl_tx_writeb(ctx, 0x05, 0x04);
/* HDMI Transcode mode enable */
mhl_tx_writeb(ctx, 0x0D, 0x1C);
mhl_tx_writeb(ctx, MHL_TX_INTR4_ENABLE_REG,
RGND_READY_MASK | CBUS_LKOUT_MASK
| MHL_DISC_FAIL_MASK | MHL_EST_MASK);
mhl_tx_writeb(ctx, MHL_TX_INTR1_ENABLE_REG, 0x60);
/* This point is very important before measure RGND impedance */
force_usb_id_switch_open(ctx);
mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL4_REG, 0, 0xF0);
mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL5_REG, 0, 0x03);
release_usb_id_switch_open(ctx);
/* Force upstream HPD to 0 when not in MHL mode */
mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, 0, 1 << 5);
mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, ~0, 1 << 4);
return sii9234_clear_error(ctx);
}
static int sii9234_goto_d3(struct sii9234 *ctx)
{
int ret;
dev_dbg(ctx->dev, "sii9234: detection started d3\n");
ret = sii9234_reset(ctx);
if (ret < 0)
goto exit;
hdmi_writeb(ctx, 0x01, 0x03);
tpi_writebm(ctx, TPI_DPD_REG, 0, 1);
/* I2C above is expected to fail because power goes down */
sii9234_clear_error(ctx);
ctx->state = ST_D3;
return 0;
exit:
dev_err(ctx->dev, "%s failed\n", __func__);
return -1;
}
static int sii9234_hw_on(struct sii9234 *ctx)
{
return regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
}
static void sii9234_hw_off(struct sii9234 *ctx)
{
gpiod_set_value(ctx->gpio_reset, 1);
msleep(20);
regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
}
static void sii9234_hw_reset(struct sii9234 *ctx)
{
gpiod_set_value(ctx->gpio_reset, 1);
msleep(20);
gpiod_set_value(ctx->gpio_reset, 0);
}
static void sii9234_cable_in(struct sii9234 *ctx)
{
int ret;
mutex_lock(&ctx->lock);
if (ctx->state != ST_OFF)
goto unlock;
ret = sii9234_hw_on(ctx);
if (ret < 0)
goto unlock;
sii9234_hw_reset(ctx);
sii9234_goto_d3(ctx);
/* To avoid irq storm, when hw is in meta state */
enable_irq(to_i2c_client(ctx->dev)->irq);
unlock:
mutex_unlock(&ctx->lock);
}
static void sii9234_cable_out(struct sii9234 *ctx)
{
mutex_lock(&ctx->lock);
if (ctx->state == ST_OFF)
goto unlock;
disable_irq(to_i2c_client(ctx->dev)->irq);
tpi_writeb(ctx, TPI_DPD_REG, 0);
/* Turn on&off hpd festure for only QCT HDMI */
sii9234_hw_off(ctx);
ctx->state = ST_OFF;
unlock:
mutex_unlock(&ctx->lock);
}
static enum sii9234_state sii9234_rgnd_ready_irq(struct sii9234 *ctx)
{
int value;
if (ctx->state == ST_D3) {
int ret;
dev_dbg(ctx->dev, "RGND_READY_INT\n");
sii9234_hw_reset(ctx);
ret = sii9234_reset(ctx);
if (ret < 0) {
dev_err(ctx->dev, "sii9234_reset() failed\n");
return ST_FAILURE;
}
return ST_RGND_INIT;
}
/* Got interrupt in inappropriate state */
if (ctx->state != ST_RGND_INIT)
return ST_FAILURE;
value = mhl_tx_readb(ctx, MHL_TX_STAT2_REG);
if (sii9234_clear_error(ctx))
return ST_FAILURE;
if ((value & RGND_INTP_MASK) != RGND_INTP_1K) {
dev_warn(ctx->dev, "RGND is not 1k\n");
return ST_RGND_INIT;
}
dev_dbg(ctx->dev, "RGND 1K!!\n");
mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL4_REG, ~0, 0x8C);
mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL5_REG, 0x77);
mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, ~0, 0x05);
if (sii9234_clear_error(ctx))
return ST_FAILURE;
msleep(T_SRC_VBUS_CBUS_TO_STABLE);
return ST_RGND_1K;
}
static enum sii9234_state sii9234_mhl_established(struct sii9234 *ctx)
{
dev_dbg(ctx->dev, "mhl est interrupt\n");
/* Discovery override */
mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL1_REG, 0x10);
/* Increase DDC translation layer timer (byte mode) */
cbus_writeb(ctx, 0x07, 0x32);
cbus_writebm(ctx, 0x44, ~0, 1 << 1);
/* Keep the discovery enabled. Need RGND interrupt */
mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL1_REG, ~0, 1);
mhl_tx_writeb(ctx, MHL_TX_INTR1_ENABLE_REG,
RSEN_CHANGE_INT_MASK | HPD_CHANGE_INT_MASK);
if (sii9234_clear_error(ctx))
return ST_FAILURE;
return ST_MHL_ESTABLISHED;
}
static enum sii9234_state sii9234_hpd_change(struct sii9234 *ctx)
{
int value;
value = cbus_readb(ctx, CBUS_MSC_REQ_ABORT_REASON_REG);
if (sii9234_clear_error(ctx))
return ST_FAILURE;
if (value & SET_HPD_DOWNSTREAM) {
/* Downstream HPD High, Enable TMDS */
sii9234_tmds_control(ctx, true);
} else {
/* Downstream HPD Low, Disable TMDS */
sii9234_tmds_control(ctx, false);
}
return ctx->state;
}
static enum sii9234_state sii9234_rsen_change(struct sii9234 *ctx)
{
int value;
/* Work_around code to handle wrong interrupt */
if (ctx->state != ST_RGND_1K) {
dev_err(ctx->dev, "RSEN_HIGH without RGND_1K\n");
return ST_FAILURE;
}
value = mhl_tx_readb(ctx, MHL_TX_SYSSTAT_REG);
if (value < 0)
return ST_FAILURE;
if (value & RSEN_STATUS) {
dev_dbg(ctx->dev, "MHL cable connected.. RSEN High\n");
return ST_RSEN_HIGH;
}
dev_dbg(ctx->dev, "RSEN lost\n");
/*
* Once RSEN loss is confirmed,we need to check
* based on cable status and chip power status,whether
* it is SINK Loss(HDMI cable not connected, TV Off)
* or MHL cable disconnection
* TODO: Define the below mhl_disconnection()
*/
msleep(T_SRC_RXSENSE_DEGLITCH);
value = mhl_tx_readb(ctx, MHL_TX_SYSSTAT_REG);
if (value < 0)
return ST_FAILURE;
dev_dbg(ctx->dev, "sys_stat: %x\n", value);
if (value & RSEN_STATUS) {
dev_dbg(ctx->dev, "RSEN recovery\n");
return ST_RSEN_HIGH;
}
dev_dbg(ctx->dev, "RSEN Really LOW\n");
/* To meet CTS 3.3.22.2 spec */
sii9234_tmds_control(ctx, false);
force_usb_id_switch_open(ctx);
release_usb_id_switch_open(ctx);
return ST_FAILURE;
}
static irqreturn_t sii9234_irq_thread(int irq, void *data)
{
struct sii9234 *ctx = data;
int intr1, intr4;
int intr1_en, intr4_en;
int cbus_intr1, cbus_intr2;
dev_dbg(ctx->dev, "%s\n", __func__);
mutex_lock(&ctx->lock);
intr1 = mhl_tx_readb(ctx, MHL_TX_INTR1_REG);
intr4 = mhl_tx_readb(ctx, MHL_TX_INTR4_REG);
intr1_en = mhl_tx_readb(ctx, MHL_TX_INTR1_ENABLE_REG);
intr4_en = mhl_tx_readb(ctx, MHL_TX_INTR4_ENABLE_REG);
cbus_intr1 = cbus_readb(ctx, CBUS_INT_STATUS_1_REG);
cbus_intr2 = cbus_readb(ctx, CBUS_INT_STATUS_2_REG);
if (sii9234_clear_error(ctx))
goto done;
dev_dbg(ctx->dev, "irq %02x/%02x %02x/%02x %02x/%02x\n",
intr1, intr1_en, intr4, intr4_en, cbus_intr1, cbus_intr2);
if (intr4 & RGND_READY_INT)
ctx->state = sii9234_rgnd_ready_irq(ctx);
if (intr1 & RSEN_CHANGE_INT)
ctx->state = sii9234_rsen_change(ctx);
if (intr4 & MHL_EST_INT)
ctx->state = sii9234_mhl_established(ctx);
if (intr1 & HPD_CHANGE_INT)
ctx->state = sii9234_hpd_change(ctx);
if (intr4 & CBUS_LKOUT_INT)
ctx->state = ST_FAILURE;
if (intr4 & MHL_DISC_FAIL_INT)
ctx->state = ST_FAILURE_DISCOVERY;
done:
/* Clean interrupt status and pending flags */
mhl_tx_writeb(ctx, MHL_TX_INTR1_REG, intr1);
mhl_tx_writeb(ctx, MHL_TX_INTR4_REG, intr4);
cbus_writeb(ctx, CBUS_MHL_STATUS_REG_0, 0xFF);
cbus_writeb(ctx, CBUS_MHL_STATUS_REG_1, 0xFF);
cbus_writeb(ctx, CBUS_INT_STATUS_1_REG, cbus_intr1);
cbus_writeb(ctx, CBUS_INT_STATUS_2_REG, cbus_intr2);
sii9234_clear_error(ctx);
if (ctx->state == ST_FAILURE) {
dev_dbg(ctx->dev, "try to reset after failure\n");
sii9234_hw_reset(ctx);
sii9234_goto_d3(ctx);
}
if (ctx->state == ST_FAILURE_DISCOVERY) {
dev_err(ctx->dev, "discovery failed, no power for MHL?\n");
tpi_writebm(ctx, TPI_DPD_REG, 0, 1);
ctx->state = ST_D3;
}
mutex_unlock(&ctx->lock);
return IRQ_HANDLED;
}
static int sii9234_init_resources(struct sii9234 *ctx,
struct i2c_client *client)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
int ret;
if (!ctx->dev->of_node) {
dev_err(ctx->dev, "not DT device\n");
return -ENODEV;
}
ctx->gpio_reset = devm_gpiod_get(ctx->dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(ctx->gpio_reset)) {
dev_err(ctx->dev, "failed to get reset gpio from DT\n");
return PTR_ERR(ctx->gpio_reset);
}
ctx->supplies[0].supply = "avcc12";
ctx->supplies[1].supply = "avcc33";
ctx->supplies[2].supply = "iovcc18";
ctx->supplies[3].supply = "cvcc12";
ret = devm_regulator_bulk_get(ctx->dev, 4, ctx->supplies);
if (ret) {
dev_err(ctx->dev, "regulator_bulk failed\n");
return ret;
}
ctx->client[I2C_MHL] = client;
ctx->client[I2C_TPI] = i2c_new_dummy(adapter, I2C_TPI_ADDR);
if (!ctx->client[I2C_TPI]) {
dev_err(ctx->dev, "failed to create TPI client\n");
return -ENODEV;
}
ctx->client[I2C_HDMI] = i2c_new_dummy(adapter, I2C_HDMI_ADDR);
if (!ctx->client[I2C_HDMI]) {
dev_err(ctx->dev, "failed to create HDMI RX client\n");
goto fail_tpi;
}
ctx->client[I2C_CBUS] = i2c_new_dummy(adapter, I2C_CBUS_ADDR);
if (!ctx->client[I2C_CBUS]) {
dev_err(ctx->dev, "failed to create CBUS client\n");
goto fail_hdmi;
}
return 0;
fail_hdmi:
i2c_unregister_device(ctx->client[I2C_HDMI]);
fail_tpi:
i2c_unregister_device(ctx->client[I2C_TPI]);
return -ENODEV;
}
static void sii9234_deinit_resources(struct sii9234 *ctx)
{
i2c_unregister_device(ctx->client[I2C_CBUS]);
i2c_unregister_device(ctx->client[I2C_HDMI]);
i2c_unregister_device(ctx->client[I2C_TPI]);
}
static inline struct sii9234 *bridge_to_sii9234(struct drm_bridge *bridge)
{
return container_of(bridge, struct sii9234, bridge);
}
static enum drm_mode_status sii9234_mode_valid(struct drm_bridge *bridge,
const struct drm_display_mode *mode)
{
if (mode->clock > MHL1_MAX_CLK)
return MODE_CLOCK_HIGH;
return MODE_OK;
}
static const struct drm_bridge_funcs sii9234_bridge_funcs = {
.mode_valid = sii9234_mode_valid,
};
static int sii9234_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct sii9234 *ctx;
struct device *dev = &client->dev;
int ret;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->dev = dev;
mutex_init(&ctx->lock);
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(dev, "I2C adapter lacks SMBUS feature\n");
return -EIO;
}
if (!client->irq) {
dev_err(dev, "no irq provided\n");
return -EINVAL;
}
irq_set_status_flags(client->irq, IRQ_NOAUTOEN);
ret = devm_request_threaded_irq(dev, client->irq, NULL,
sii9234_irq_thread,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"sii9234", ctx);
if (ret < 0) {
dev_err(dev, "failed to install IRQ handler\n");
return ret;
}
ret = sii9234_init_resources(ctx, client);
if (ret < 0)
return ret;
i2c_set_clientdata(client, ctx);
ctx->bridge.funcs = &sii9234_bridge_funcs;
ctx->bridge.of_node = dev->of_node;
drm_bridge_add(&ctx->bridge);
sii9234_cable_in(ctx);
return 0;
}
static int sii9234_remove(struct i2c_client *client)
{
struct sii9234 *ctx = i2c_get_clientdata(client);
sii9234_cable_out(ctx);
drm_bridge_remove(&ctx->bridge);
sii9234_deinit_resources(ctx);
return 0;
}
static const struct of_device_id sii9234_dt_match[] = {
{ .compatible = "sil,sii9234" },
{ },
};
MODULE_DEVICE_TABLE(of, sii9234_dt_match);
static const struct i2c_device_id sii9234_id[] = {
{ "SII9234", 0 },
{ },
};
MODULE_DEVICE_TABLE(i2c, sii9234_id);
static struct i2c_driver sii9234_driver = {
.driver = {
.name = "sii9234",
.of_match_table = sii9234_dt_match,
},
.probe = sii9234_probe,
.remove = sii9234_remove,
.id_table = sii9234_id,
};
module_i2c_driver(sii9234_driver);
MODULE_LICENSE("GPL");
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <media/rc-core.h>
#include "sil-sii8620.h" #include "sil-sii8620.h"
#define SII8620_BURST_BUF_LEN 288 #define SII8620_BURST_BUF_LEN 288
...@@ -58,6 +60,7 @@ enum sii8620_mt_state { ...@@ -58,6 +60,7 @@ enum sii8620_mt_state {
struct sii8620 { struct sii8620 {
struct drm_bridge bridge; struct drm_bridge bridge;
struct device *dev; struct device *dev;
struct rc_dev *rc_dev;
struct clk *clk_xtal; struct clk *clk_xtal;
struct gpio_desc *gpio_reset; struct gpio_desc *gpio_reset;
struct gpio_desc *gpio_int; struct gpio_desc *gpio_int;
...@@ -431,6 +434,16 @@ static void sii8620_mt_rap(struct sii8620 *ctx, u8 code) ...@@ -431,6 +434,16 @@ static void sii8620_mt_rap(struct sii8620 *ctx, u8 code)
sii8620_mt_msc_msg(ctx, MHL_MSC_MSG_RAP, code); sii8620_mt_msc_msg(ctx, MHL_MSC_MSG_RAP, code);
} }
static void sii8620_mt_rcpk(struct sii8620 *ctx, u8 code)
{
sii8620_mt_msc_msg(ctx, MHL_MSC_MSG_RCPK, code);
}
static void sii8620_mt_rcpe(struct sii8620 *ctx, u8 code)
{
sii8620_mt_msc_msg(ctx, MHL_MSC_MSG_RCPE, code);
}
static void sii8620_mt_read_devcap_send(struct sii8620 *ctx, static void sii8620_mt_read_devcap_send(struct sii8620 *ctx,
struct sii8620_mt_msg *msg) struct sii8620_mt_msg *msg)
{ {
...@@ -1753,6 +1766,25 @@ static void sii8620_send_features(struct sii8620 *ctx) ...@@ -1753,6 +1766,25 @@ static void sii8620_send_features(struct sii8620 *ctx)
sii8620_write_buf(ctx, REG_MDT_XMIT_WRITE_PORT, buf, ARRAY_SIZE(buf)); sii8620_write_buf(ctx, REG_MDT_XMIT_WRITE_PORT, buf, ARRAY_SIZE(buf));
} }
static bool sii8620_rcp_consume(struct sii8620 *ctx, u8 scancode)
{
bool pressed = !(scancode & MHL_RCP_KEY_RELEASED_MASK);
scancode &= MHL_RCP_KEY_ID_MASK;
if (!ctx->rc_dev) {
dev_dbg(ctx->dev, "RCP input device not initialized\n");
return false;
}
if (pressed)
rc_keydown(ctx->rc_dev, RC_PROTO_CEC, scancode, 0);
else
rc_keyup(ctx->rc_dev);
return true;
}
static void sii8620_msc_mr_set_int(struct sii8620 *ctx) static void sii8620_msc_mr_set_int(struct sii8620 *ctx)
{ {
u8 ints[MHL_INT_SIZE]; u8 ints[MHL_INT_SIZE];
...@@ -1804,19 +1836,25 @@ static void sii8620_msc_mt_done(struct sii8620 *ctx) ...@@ -1804,19 +1836,25 @@ static void sii8620_msc_mt_done(struct sii8620 *ctx)
static void sii8620_msc_mr_msc_msg(struct sii8620 *ctx) static void sii8620_msc_mr_msc_msg(struct sii8620 *ctx)
{ {
struct sii8620_mt_msg *msg = sii8620_msc_msg_first(ctx); struct sii8620_mt_msg *msg;
u8 buf[2]; u8 buf[2];
if (!msg)
return;
sii8620_read_buf(ctx, REG_MSC_MR_MSC_MSG_RCVD_1ST_DATA, buf, 2); sii8620_read_buf(ctx, REG_MSC_MR_MSC_MSG_RCVD_1ST_DATA, buf, 2);
switch (buf[0]) { switch (buf[0]) {
case MHL_MSC_MSG_RAPK: case MHL_MSC_MSG_RAPK:
msg = sii8620_msc_msg_first(ctx);
if (!msg)
return;
msg->ret = buf[1]; msg->ret = buf[1];
ctx->mt_state = MT_STATE_DONE; ctx->mt_state = MT_STATE_DONE;
break; break;
case MHL_MSC_MSG_RCP:
if (!sii8620_rcp_consume(ctx, buf[1]))
sii8620_mt_rcpe(ctx,
MHL_RCPE_STATUS_INEFFECTIVE_KEY_CODE);
sii8620_mt_rcpk(ctx, buf[1]);
break;
default: default:
dev_err(ctx->dev, "%s message type %d,%d not supported", dev_err(ctx->dev, "%s message type %d,%d not supported",
__func__, buf[0], buf[1]); __func__, buf[0], buf[1]);
...@@ -2102,11 +2140,57 @@ static void sii8620_cable_in(struct sii8620 *ctx) ...@@ -2102,11 +2140,57 @@ static void sii8620_cable_in(struct sii8620 *ctx)
enable_irq(to_i2c_client(ctx->dev)->irq); enable_irq(to_i2c_client(ctx->dev)->irq);
} }
static void sii8620_init_rcp_input_dev(struct sii8620 *ctx)
{
struct rc_dev *rc_dev;
int ret;
rc_dev = rc_allocate_device(RC_DRIVER_SCANCODE);
if (!rc_dev) {
dev_err(ctx->dev, "Failed to allocate RC device\n");
ctx->error = -ENOMEM;
return;
}
rc_dev->input_phys = "sii8620/input0";
rc_dev->input_id.bustype = BUS_VIRTUAL;
rc_dev->map_name = RC_MAP_CEC;
rc_dev->allowed_protocols = RC_PROTO_BIT_CEC;
rc_dev->driver_name = "sii8620";
rc_dev->device_name = "sii8620";
ret = rc_register_device(rc_dev);
if (ret) {
dev_err(ctx->dev, "Failed to register RC device\n");
ctx->error = ret;
rc_free_device(ctx->rc_dev);
return;
}
ctx->rc_dev = rc_dev;
}
static inline struct sii8620 *bridge_to_sii8620(struct drm_bridge *bridge) static inline struct sii8620 *bridge_to_sii8620(struct drm_bridge *bridge)
{ {
return container_of(bridge, struct sii8620, bridge); return container_of(bridge, struct sii8620, bridge);
} }
static int sii8620_attach(struct drm_bridge *bridge)
{
struct sii8620 *ctx = bridge_to_sii8620(bridge);
sii8620_init_rcp_input_dev(ctx);
return sii8620_clear_error(ctx);
}
static void sii8620_detach(struct drm_bridge *bridge)
{
struct sii8620 *ctx = bridge_to_sii8620(bridge);
rc_unregister_device(ctx->rc_dev);
}
static bool sii8620_mode_fixup(struct drm_bridge *bridge, static bool sii8620_mode_fixup(struct drm_bridge *bridge,
const struct drm_display_mode *mode, const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode) struct drm_display_mode *adjusted_mode)
...@@ -2151,6 +2235,8 @@ static bool sii8620_mode_fixup(struct drm_bridge *bridge, ...@@ -2151,6 +2235,8 @@ static bool sii8620_mode_fixup(struct drm_bridge *bridge,
} }
static const struct drm_bridge_funcs sii8620_bridge_funcs = { static const struct drm_bridge_funcs sii8620_bridge_funcs = {
.attach = sii8620_attach,
.detach = sii8620_detach,
.mode_fixup = sii8620_mode_fixup, .mode_fixup = sii8620_mode_fixup,
}; };
...@@ -2217,8 +2303,8 @@ static int sii8620_remove(struct i2c_client *client) ...@@ -2217,8 +2303,8 @@ static int sii8620_remove(struct i2c_client *client)
struct sii8620 *ctx = i2c_get_clientdata(client); struct sii8620 *ctx = i2c_get_clientdata(client);
disable_irq(to_i2c_client(ctx->dev)->irq); disable_irq(to_i2c_client(ctx->dev)->irq);
drm_bridge_remove(&ctx->bridge);
sii8620_hw_off(ctx); sii8620_hw_off(ctx);
drm_bridge_remove(&ctx->bridge);
return 0; return 0;
} }
......
...@@ -221,7 +221,6 @@ struct dw_mipi_dsi { ...@@ -221,7 +221,6 @@ struct dw_mipi_dsi {
struct drm_bridge bridge; struct drm_bridge bridge;
struct mipi_dsi_host dsi_host; struct mipi_dsi_host dsi_host;
struct drm_bridge *panel_bridge; struct drm_bridge *panel_bridge;
bool is_panel_bridge;
struct device *dev; struct device *dev;
void __iomem *base; void __iomem *base;
...@@ -297,7 +296,6 @@ static int dw_mipi_dsi_host_attach(struct mipi_dsi_host *host, ...@@ -297,7 +296,6 @@ static int dw_mipi_dsi_host_attach(struct mipi_dsi_host *host,
bridge = drm_panel_bridge_add(panel, DRM_MODE_CONNECTOR_DSI); bridge = drm_panel_bridge_add(panel, DRM_MODE_CONNECTOR_DSI);
if (IS_ERR(bridge)) if (IS_ERR(bridge))
return PTR_ERR(bridge); return PTR_ERR(bridge);
dsi->is_panel_bridge = true;
} }
dsi->panel_bridge = bridge; dsi->panel_bridge = bridge;
...@@ -312,8 +310,7 @@ static int dw_mipi_dsi_host_detach(struct mipi_dsi_host *host, ...@@ -312,8 +310,7 @@ static int dw_mipi_dsi_host_detach(struct mipi_dsi_host *host,
{ {
struct dw_mipi_dsi *dsi = host_to_dsi(host); struct dw_mipi_dsi *dsi = host_to_dsi(host);
if (dsi->is_panel_bridge) drm_of_panel_bridge_remove(host->dev->of_node, 1, 0);
drm_panel_bridge_remove(dsi->panel_bridge);
drm_bridge_remove(&dsi->bridge); drm_bridge_remove(&dsi->bridge);
......
...@@ -182,9 +182,6 @@ void drm_atomic_state_default_clear(struct drm_atomic_state *state) ...@@ -182,9 +182,6 @@ void drm_atomic_state_default_clear(struct drm_atomic_state *state)
for (i = 0; i < state->num_private_objs; i++) { for (i = 0; i < state->num_private_objs; i++) {
struct drm_private_obj *obj = state->private_objs[i].ptr; struct drm_private_obj *obj = state->private_objs[i].ptr;
if (!obj)
continue;
obj->funcs->atomic_destroy_state(obj, obj->funcs->atomic_destroy_state(obj,
state->private_objs[i].state); state->private_objs[i].state);
state->private_objs[i].ptr = NULL; state->private_objs[i].ptr = NULL;
......
...@@ -3052,6 +3052,7 @@ int drm_atomic_helper_resume(struct drm_device *dev, ...@@ -3052,6 +3052,7 @@ int drm_atomic_helper_resume(struct drm_device *dev,
drm_modeset_backoff(&ctx); drm_modeset_backoff(&ctx);
} }
drm_atomic_state_put(state);
drm_modeset_drop_locks(&ctx); drm_modeset_drop_locks(&ctx);
drm_modeset_acquire_fini(&ctx); drm_modeset_acquire_fini(&ctx);
......
...@@ -137,8 +137,10 @@ EXPORT_SYMBOL(drm_dp_link_train_channel_eq_delay); ...@@ -137,8 +137,10 @@ EXPORT_SYMBOL(drm_dp_link_train_channel_eq_delay);
u8 drm_dp_link_rate_to_bw_code(int link_rate) u8 drm_dp_link_rate_to_bw_code(int link_rate)
{ {
switch (link_rate) { switch (link_rate) {
case 162000:
default: default:
WARN(1, "unknown DP link rate %d, using %x\n", link_rate,
DP_LINK_BW_1_62);
case 162000:
return DP_LINK_BW_1_62; return DP_LINK_BW_1_62;
case 270000: case 270000:
return DP_LINK_BW_2_7; return DP_LINK_BW_2_7;
...@@ -151,8 +153,9 @@ EXPORT_SYMBOL(drm_dp_link_rate_to_bw_code); ...@@ -151,8 +153,9 @@ EXPORT_SYMBOL(drm_dp_link_rate_to_bw_code);
int drm_dp_bw_code_to_link_rate(u8 link_bw) int drm_dp_bw_code_to_link_rate(u8 link_bw)
{ {
switch (link_bw) { switch (link_bw) {
case DP_LINK_BW_1_62:
default: default:
WARN(1, "unknown DP link BW code %x, using 162000\n", link_bw);
case DP_LINK_BW_1_62:
return 162000; return 162000;
case DP_LINK_BW_2_7: case DP_LINK_BW_2_7:
return 270000; return 270000;
......
...@@ -27,19 +27,24 @@ ...@@ -27,19 +27,24 @@
* DOC: overview * DOC: overview
* *
* This library provides helpers for drivers that don't subclass * This library provides helpers for drivers that don't subclass
* &drm_framebuffer and and use &drm_gem_object for their backing storage. * &drm_framebuffer and use &drm_gem_object for their backing storage.
* *
* Drivers without additional needs to validate framebuffers can simply use * Drivers without additional needs to validate framebuffers can simply use
* drm_gem_fb_create() and everything is wired up automatically. But all * drm_gem_fb_create() and everything is wired up automatically. Other drivers
* parts can be used individually. * can use all parts independently.
*/ */
/** /**
* drm_gem_fb_get_obj() - Get GEM object for framebuffer * drm_gem_fb_get_obj() - Get GEM object backing the framebuffer
* @fb: The framebuffer * @fb: Framebuffer
* @plane: Which plane * @plane: Plane index
* *
* Returns the GEM object for given framebuffer. * No additional reference is taken beyond the one that the &drm_frambuffer
* already holds.
*
* Returns:
* Pointer to &drm_gem_object for the given framebuffer and plane index or NULL
* if it does not exist.
*/ */
struct drm_gem_object *drm_gem_fb_get_obj(struct drm_framebuffer *fb, struct drm_gem_object *drm_gem_fb_get_obj(struct drm_framebuffer *fb,
unsigned int plane) unsigned int plane)
...@@ -82,7 +87,7 @@ drm_gem_fb_alloc(struct drm_device *dev, ...@@ -82,7 +87,7 @@ drm_gem_fb_alloc(struct drm_device *dev,
/** /**
* drm_gem_fb_destroy - Free GEM backed framebuffer * drm_gem_fb_destroy - Free GEM backed framebuffer
* @fb: DRM framebuffer * @fb: Framebuffer
* *
* Frees a GEM backed framebuffer with its backing buffer(s) and the structure * Frees a GEM backed framebuffer with its backing buffer(s) and the structure
* itself. Drivers can use this as their &drm_framebuffer_funcs->destroy * itself. Drivers can use this as their &drm_framebuffer_funcs->destroy
...@@ -102,12 +107,13 @@ EXPORT_SYMBOL(drm_gem_fb_destroy); ...@@ -102,12 +107,13 @@ EXPORT_SYMBOL(drm_gem_fb_destroy);
/** /**
* drm_gem_fb_create_handle - Create handle for GEM backed framebuffer * drm_gem_fb_create_handle - Create handle for GEM backed framebuffer
* @fb: DRM framebuffer * @fb: Framebuffer
* @file: drm file * @file: DRM file to register the handle for
* @handle: handle created * @handle: Pointer to return the created handle
* *
* This function creates a handle for the GEM object backing the framebuffer.
* Drivers can use this as their &drm_framebuffer_funcs->create_handle * Drivers can use this as their &drm_framebuffer_funcs->create_handle
* callback. * callback. The GETFB IOCTL calls into this callback.
* *
* Returns: * Returns:
* 0 on success or a negative error code on failure. * 0 on success or a negative error code on failure.
...@@ -120,18 +126,21 @@ int drm_gem_fb_create_handle(struct drm_framebuffer *fb, struct drm_file *file, ...@@ -120,18 +126,21 @@ int drm_gem_fb_create_handle(struct drm_framebuffer *fb, struct drm_file *file,
EXPORT_SYMBOL(drm_gem_fb_create_handle); EXPORT_SYMBOL(drm_gem_fb_create_handle);
/** /**
* drm_gem_fb_create_with_funcs() - helper function for the * drm_gem_fb_create_with_funcs() - Helper function for the
* &drm_mode_config_funcs.fb_create * &drm_mode_config_funcs.fb_create
* callback * callback
* @dev: DRM device * @dev: DRM device
* @file: drm file for the ioctl call * @file: DRM file that holds the GEM handle(s) backing the framebuffer
* @mode_cmd: metadata from the userspace fb creation request * @mode_cmd: Metadata from the userspace framebuffer creation request
* @funcs: vtable to be used for the new framebuffer object * @funcs: vtable to be used for the new framebuffer object
* *
* This can be used to set &drm_framebuffer_funcs for drivers that need the * This can be used to set &drm_framebuffer_funcs for drivers that need the
* &drm_framebuffer_funcs.dirty callback. Use drm_gem_fb_create() if you don't * &drm_framebuffer_funcs.dirty callback. Use drm_gem_fb_create() if you don't
* need to change &drm_framebuffer_funcs. * need to change &drm_framebuffer_funcs.
* The function does buffer size validation. * The function does buffer size validation.
*
* Returns:
* Pointer to a &drm_framebuffer on success or an error pointer on failure.
*/ */
struct drm_framebuffer * struct drm_framebuffer *
drm_gem_fb_create_with_funcs(struct drm_device *dev, struct drm_file *file, drm_gem_fb_create_with_funcs(struct drm_device *dev, struct drm_file *file,
...@@ -192,15 +201,26 @@ static const struct drm_framebuffer_funcs drm_gem_fb_funcs = { ...@@ -192,15 +201,26 @@ static const struct drm_framebuffer_funcs drm_gem_fb_funcs = {
}; };
/** /**
* drm_gem_fb_create() - &drm_mode_config_funcs.fb_create callback function * drm_gem_fb_create() - Helper function for the
* &drm_mode_config_funcs.fb_create callback
* @dev: DRM device * @dev: DRM device
* @file: drm file for the ioctl call * @file: DRM file that holds the GEM handle(s) backing the framebuffer
* @mode_cmd: metadata from the userspace fb creation request * @mode_cmd: Metadata from the userspace framebuffer creation request
*
* This function creates a new framebuffer object described by
* &drm_mode_fb_cmd2. This description includes handles for the buffer(s)
* backing the framebuffer.
* *
* If your hardware has special alignment or pitch requirements these should be * If your hardware has special alignment or pitch requirements these should be
* checked before calling this function. The function does buffer size * checked before calling this function. The function does buffer size
* validation. Use drm_gem_fb_create_with_funcs() if you need to set * validation. Use drm_gem_fb_create_with_funcs() if you need to set
* &drm_framebuffer_funcs.dirty. * &drm_framebuffer_funcs.dirty.
*
* Drivers can use this as their &drm_mode_config_funcs.fb_create callback.
* The ADDFB2 IOCTL calls into this callback.
*
* Returns:
* Pointer to a &drm_framebuffer on success or an error pointer on failure.
*/ */
struct drm_framebuffer * struct drm_framebuffer *
drm_gem_fb_create(struct drm_device *dev, struct drm_file *file, drm_gem_fb_create(struct drm_device *dev, struct drm_file *file,
...@@ -212,15 +232,15 @@ drm_gem_fb_create(struct drm_device *dev, struct drm_file *file, ...@@ -212,15 +232,15 @@ drm_gem_fb_create(struct drm_device *dev, struct drm_file *file,
EXPORT_SYMBOL_GPL(drm_gem_fb_create); EXPORT_SYMBOL_GPL(drm_gem_fb_create);
/** /**
* drm_gem_fb_prepare_fb() - Prepare gem framebuffer * drm_gem_fb_prepare_fb() - Prepare a GEM backed framebuffer
* @plane: Which plane * @plane: Plane
* @state: Plane state attach fence to * @state: Plane state the fence will be attached to
* *
* This can be used as the &drm_plane_helper_funcs.prepare_fb hook. * This function prepares a GEM backed framebuffer for scanout by checking if
* * the plane framebuffer has a DMA-BUF attached. If it does, it extracts the
* This function checks if the plane FB has an dma-buf attached, extracts * exclusive fence and attaches it to the plane state for the atomic helper to
* the exclusive fence and attaches it to plane state for the atomic helper * wait on. This function can be used as the &drm_plane_helper_funcs.prepare_fb
* to wait on. * callback.
* *
* There is no need for &drm_plane_helper_funcs.cleanup_fb hook for simple * There is no need for &drm_plane_helper_funcs.cleanup_fb hook for simple
* gem based framebuffer drivers which have their buffers always pinned in * gem based framebuffer drivers which have their buffers always pinned in
...@@ -246,17 +266,19 @@ int drm_gem_fb_prepare_fb(struct drm_plane *plane, ...@@ -246,17 +266,19 @@ int drm_gem_fb_prepare_fb(struct drm_plane *plane,
EXPORT_SYMBOL_GPL(drm_gem_fb_prepare_fb); EXPORT_SYMBOL_GPL(drm_gem_fb_prepare_fb);
/** /**
* drm_gem_fbdev_fb_create - Create a drm_framebuffer for fbdev emulation * drm_gem_fbdev_fb_create - Create a GEM backed &drm_framebuffer for fbdev
* emulation
* @dev: DRM device * @dev: DRM device
* @sizes: fbdev size description * @sizes: fbdev size description
* @pitch_align: optional pitch alignment * @pitch_align: Optional pitch alignment
* @obj: GEM object backing the framebuffer * @obj: GEM object backing the framebuffer
* @funcs: vtable to be used for the new framebuffer object * @funcs: vtable to be used for the new framebuffer object
* *
* This function creates a framebuffer for use with fbdev emulation. * This function creates a framebuffer from a &drm_fb_helper_surface_size
* description for use in the &drm_fb_helper_funcs.fb_probe callback.
* *
* Returns: * Returns:
* Pointer to a drm_framebuffer on success or an error pointer on failure. * Pointer to a &drm_framebuffer on success or an error pointer on failure.
*/ */
struct drm_framebuffer * struct drm_framebuffer *
drm_gem_fbdev_fb_create(struct drm_device *dev, drm_gem_fbdev_fb_create(struct drm_device *dev,
......
...@@ -262,3 +262,36 @@ int drm_of_find_panel_or_bridge(const struct device_node *np, ...@@ -262,3 +262,36 @@ int drm_of_find_panel_or_bridge(const struct device_node *np,
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(drm_of_find_panel_or_bridge); EXPORT_SYMBOL_GPL(drm_of_find_panel_or_bridge);
#ifdef CONFIG_DRM_PANEL_BRIDGE
/*
* drm_of_panel_bridge_remove - remove panel bridge
* @np: device tree node containing panel bridge output ports
*
* Remove the panel bridge of a given DT node's port and endpoint number
*
* Returns zero if successful, or one of the standard error codes if it fails.
*/
int drm_of_panel_bridge_remove(const struct device_node *np,
int port, int endpoint)
{
struct drm_bridge *bridge;
struct device_node *remote;
remote = of_graph_get_remote_node(np, port, endpoint);
if (!remote)
return -ENODEV;
bridge = of_drm_find_bridge(remote);
drm_panel_bridge_remove(bridge);
return 0;
}
#else
int drm_of_panel_bridge_remove(const struct device_node *np,
int port, int endpoint)
{
return -EINVAL;
}
#endif
EXPORT_SYMBOL_GPL(drm_of_panel_bridge_remove);
...@@ -599,7 +599,7 @@ static u32 mdp5_get_vblank_counter(struct drm_device *dev, unsigned int pipe) ...@@ -599,7 +599,7 @@ static u32 mdp5_get_vblank_counter(struct drm_device *dev, unsigned int pipe)
struct drm_crtc *crtc; struct drm_crtc *crtc;
struct drm_encoder *encoder; struct drm_encoder *encoder;
if (pipe < 0 || pipe >= priv->num_crtcs) if (pipe >= priv->num_crtcs)
return 0; return 0;
crtc = priv->crtcs[pipe]; crtc = priv->crtcs[pipe];
......
...@@ -82,6 +82,14 @@ config DRM_PANEL_PANASONIC_VVX10F034N00 ...@@ -82,6 +82,14 @@ config DRM_PANEL_PANASONIC_VVX10F034N00
WUXGA (1920x1200) Novatek NT1397-based DSI panel as found in some WUXGA (1920x1200) Novatek NT1397-based DSI panel as found in some
Xperia Z2 tablets Xperia Z2 tablets
config DRM_PANEL_RASPBERRYPI_TOUCHSCREEN
tristate "Raspberry Pi 7-inch touchscreen panel"
depends on DRM_MIPI_DSI
help
Say Y here if you want to enable support for the Raspberry
Pi 7" Touchscreen. To compile this driver as a module,
choose M here.
config DRM_PANEL_SAMSUNG_S6E3HA2 config DRM_PANEL_SAMSUNG_S6E3HA2
tristate "Samsung S6E3HA2 DSI video mode panel" tristate "Samsung S6E3HA2 DSI video mode panel"
depends on OF depends on OF
......
...@@ -5,6 +5,7 @@ obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o ...@@ -5,6 +5,7 @@ obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o
obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o
obj-$(CONFIG_DRM_PANEL_ORISETECH_OTM8009A) += panel-orisetech-otm8009a.o obj-$(CONFIG_DRM_PANEL_ORISETECH_OTM8009A) += panel-orisetech-otm8009a.o
obj-$(CONFIG_DRM_PANEL_PANASONIC_VVX10F034N00) += panel-panasonic-vvx10f034n00.o obj-$(CONFIG_DRM_PANEL_PANASONIC_VVX10F034N00) += panel-panasonic-vvx10f034n00.o
obj-$(CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN) += panel-raspberrypi-touchscreen.o
obj-$(CONFIG_DRM_PANEL_SAMSUNG_LD9040) += panel-samsung-ld9040.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_LD9040) += panel-samsung-ld9040.o
obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E3HA2) += panel-samsung-s6e3ha2.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E3HA2) += panel-samsung-s6e3ha2.o
obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63J0X03) += panel-samsung-s6e63j0x03.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63J0X03) += panel-samsung-s6e63j0x03.o
......
/*
* Copyright © 2016-2017 Broadcom
*
* 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.
*
* Portions of this file (derived from panel-simple.c) are:
*
* Copyright (C) 2013, NVIDIA Corporation. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sub license,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
/**
* Raspberry Pi 7" touchscreen panel driver.
*
* The 7" touchscreen consists of a DPI LCD panel, a Toshiba
* TC358762XBG DSI-DPI bridge, and an I2C-connected Atmel ATTINY88-MUR
* controlling power management, the LCD PWM, and initial register
* setup of the Tohsiba.
*
* This driver controls the TC358762 and ATTINY88, presenting a DSI
* device with a drm_panel.
*/
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/fb.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_graph.h>
#include <linux/pm.h>
#include <drm/drm_panel.h>
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_panel.h>
#define RPI_DSI_DRIVER_NAME "rpi-ts-dsi"
/* I2C registers of the Atmel microcontroller. */
enum REG_ADDR {
REG_ID = 0x80,
REG_PORTA, /* BIT(2) for horizontal flip, BIT(3) for vertical flip */
REG_PORTB,
REG_PORTC,
REG_PORTD,
REG_POWERON,
REG_PWM,
REG_DDRA,
REG_DDRB,
REG_DDRC,
REG_DDRD,
REG_TEST,
REG_WR_ADDRL,
REG_WR_ADDRH,
REG_READH,
REG_READL,
REG_WRITEH,
REG_WRITEL,
REG_ID2,
};
/* DSI D-PHY Layer Registers */
#define D0W_DPHYCONTTX 0x0004
#define CLW_DPHYCONTRX 0x0020
#define D0W_DPHYCONTRX 0x0024
#define D1W_DPHYCONTRX 0x0028
#define COM_DPHYCONTRX 0x0038
#define CLW_CNTRL 0x0040
#define D0W_CNTRL 0x0044
#define D1W_CNTRL 0x0048
#define DFTMODE_CNTRL 0x0054
/* DSI PPI Layer Registers */
#define PPI_STARTPPI 0x0104
#define PPI_BUSYPPI 0x0108
#define PPI_LINEINITCNT 0x0110
#define PPI_LPTXTIMECNT 0x0114
#define PPI_CLS_ATMR 0x0140
#define PPI_D0S_ATMR 0x0144
#define PPI_D1S_ATMR 0x0148
#define PPI_D0S_CLRSIPOCOUNT 0x0164
#define PPI_D1S_CLRSIPOCOUNT 0x0168
#define CLS_PRE 0x0180
#define D0S_PRE 0x0184
#define D1S_PRE 0x0188
#define CLS_PREP 0x01A0
#define D0S_PREP 0x01A4
#define D1S_PREP 0x01A8
#define CLS_ZERO 0x01C0
#define D0S_ZERO 0x01C4
#define D1S_ZERO 0x01C8
#define PPI_CLRFLG 0x01E0
#define PPI_CLRSIPO 0x01E4
#define HSTIMEOUT 0x01F0
#define HSTIMEOUTENABLE 0x01F4
/* DSI Protocol Layer Registers */
#define DSI_STARTDSI 0x0204
#define DSI_BUSYDSI 0x0208
#define DSI_LANEENABLE 0x0210
# define DSI_LANEENABLE_CLOCK BIT(0)
# define DSI_LANEENABLE_D0 BIT(1)
# define DSI_LANEENABLE_D1 BIT(2)
#define DSI_LANESTATUS0 0x0214
#define DSI_LANESTATUS1 0x0218
#define DSI_INTSTATUS 0x0220
#define DSI_INTMASK 0x0224
#define DSI_INTCLR 0x0228
#define DSI_LPTXTO 0x0230
#define DSI_MODE 0x0260
#define DSI_PAYLOAD0 0x0268
#define DSI_PAYLOAD1 0x026C
#define DSI_SHORTPKTDAT 0x0270
#define DSI_SHORTPKTREQ 0x0274
#define DSI_BTASTA 0x0278
#define DSI_BTACLR 0x027C
/* DSI General Registers */
#define DSIERRCNT 0x0300
#define DSISIGMOD 0x0304
/* DSI Application Layer Registers */
#define APLCTRL 0x0400
#define APLSTAT 0x0404
#define APLERR 0x0408
#define PWRMOD 0x040C
#define RDPKTLN 0x0410
#define PXLFMT 0x0414
#define MEMWRCMD 0x0418
/* LCDC/DPI Host Registers */
#define LCDCTRL 0x0420
#define HSR 0x0424
#define HDISPR 0x0428
#define VSR 0x042C
#define VDISPR 0x0430
#define VFUEN 0x0434
/* DBI-B Host Registers */
#define DBIBCTRL 0x0440
/* SPI Master Registers */
#define SPICMR 0x0450
#define SPITCR 0x0454
/* System Controller Registers */
#define SYSSTAT 0x0460
#define SYSCTRL 0x0464
#define SYSPLL1 0x0468
#define SYSPLL2 0x046C
#define SYSPLL3 0x0470
#define SYSPMCTRL 0x047C
/* GPIO Registers */
#define GPIOC 0x0480
#define GPIOO 0x0484
#define GPIOI 0x0488
/* I2C Registers */
#define I2CCLKCTRL 0x0490
/* Chip/Rev Registers */
#define IDREG 0x04A0
/* Debug Registers */
#define WCMDQUEUE 0x0500
#define RCMDQUEUE 0x0504
struct rpi_touchscreen {
struct drm_panel base;
struct mipi_dsi_device *dsi;
struct i2c_client *i2c;
};
static const struct drm_display_mode rpi_touchscreen_modes[] = {
{
/* Modeline comes from the Raspberry Pi firmware, with HFP=1
* plugged in and clock re-computed from that.
*/
.clock = 25979400 / 1000,
.hdisplay = 800,
.hsync_start = 800 + 1,
.hsync_end = 800 + 1 + 2,
.htotal = 800 + 1 + 2 + 46,
.vdisplay = 480,
.vsync_start = 480 + 7,
.vsync_end = 480 + 7 + 2,
.vtotal = 480 + 7 + 2 + 21,
.vrefresh = 60,
},
};
static struct rpi_touchscreen *panel_to_ts(struct drm_panel *panel)
{
return container_of(panel, struct rpi_touchscreen, base);
}
static u8 rpi_touchscreen_i2c_read(struct rpi_touchscreen *ts, u8 reg)
{
return i2c_smbus_read_byte_data(ts->i2c, reg);
}
static void rpi_touchscreen_i2c_write(struct rpi_touchscreen *ts,
u8 reg, u8 val)
{
int ret;
ret = i2c_smbus_write_byte_data(ts->i2c, reg, val);
if (ret)
dev_err(&ts->dsi->dev, "I2C write failed: %d\n", ret);
}
static int rpi_touchscreen_write(struct rpi_touchscreen *ts, u16 reg, u32 val)
{
#if 0
/* The firmware uses LP DSI transactions like this to bring up
* the hardware, which should be faster than using I2C to then
* pass to the Toshiba. However, I was unable to get it to
* work.
*/
u8 msg[] = {
reg,
reg >> 8,
val,
val >> 8,
val >> 16,
val >> 24,
};
mipi_dsi_dcs_write_buffer(ts->dsi, msg, sizeof(msg));
#else
rpi_touchscreen_i2c_write(ts, REG_WR_ADDRH, reg >> 8);
rpi_touchscreen_i2c_write(ts, REG_WR_ADDRL, reg);
rpi_touchscreen_i2c_write(ts, REG_WRITEH, val >> 8);
rpi_touchscreen_i2c_write(ts, REG_WRITEL, val);
#endif
return 0;
}
static int rpi_touchscreen_disable(struct drm_panel *panel)
{
struct rpi_touchscreen *ts = panel_to_ts(panel);
rpi_touchscreen_i2c_write(ts, REG_PWM, 0);
rpi_touchscreen_i2c_write(ts, REG_POWERON, 0);
udelay(1);
return 0;
}
static int rpi_touchscreen_noop(struct drm_panel *panel)
{
return 0;
}
static int rpi_touchscreen_enable(struct drm_panel *panel)
{
struct rpi_touchscreen *ts = panel_to_ts(panel);
int i;
rpi_touchscreen_i2c_write(ts, REG_POWERON, 1);
/* Wait for nPWRDWN to go low to indicate poweron is done. */
for (i = 0; i < 100; i++) {
if (rpi_touchscreen_i2c_read(ts, REG_PORTB) & 1)
break;
}
rpi_touchscreen_write(ts, DSI_LANEENABLE,
DSI_LANEENABLE_CLOCK |
DSI_LANEENABLE_D0);
rpi_touchscreen_write(ts, PPI_D0S_CLRSIPOCOUNT, 0x05);
rpi_touchscreen_write(ts, PPI_D1S_CLRSIPOCOUNT, 0x05);
rpi_touchscreen_write(ts, PPI_D0S_ATMR, 0x00);
rpi_touchscreen_write(ts, PPI_D1S_ATMR, 0x00);
rpi_touchscreen_write(ts, PPI_LPTXTIMECNT, 0x03);
rpi_touchscreen_write(ts, SPICMR, 0x00);
rpi_touchscreen_write(ts, LCDCTRL, 0x00100150);
rpi_touchscreen_write(ts, SYSCTRL, 0x040f);
msleep(100);
rpi_touchscreen_write(ts, PPI_STARTPPI, 0x01);
rpi_touchscreen_write(ts, DSI_STARTDSI, 0x01);
msleep(100);
/* Turn on the backlight. */
rpi_touchscreen_i2c_write(ts, REG_PWM, 255);
/* Default to the same orientation as the closed source
* firmware used for the panel. Runtime rotation
* configuration will be supported using VC4's plane
* orientation bits.
*/
rpi_touchscreen_i2c_write(ts, REG_PORTA, BIT(2));
return 0;
}
static int rpi_touchscreen_get_modes(struct drm_panel *panel)
{
struct drm_connector *connector = panel->connector;
struct drm_device *drm = panel->drm;
unsigned int i, num = 0;
static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
for (i = 0; i < ARRAY_SIZE(rpi_touchscreen_modes); i++) {
const struct drm_display_mode *m = &rpi_touchscreen_modes[i];
struct drm_display_mode *mode;
mode = drm_mode_duplicate(drm, m);
if (!mode) {
dev_err(drm->dev, "failed to add mode %ux%u@%u\n",
m->hdisplay, m->vdisplay, m->vrefresh);
continue;
}
mode->type |= DRM_MODE_TYPE_DRIVER;
if (i == 0)
mode->type |= DRM_MODE_TYPE_PREFERRED;
drm_mode_set_name(mode);
drm_mode_probed_add(connector, mode);
num++;
}
connector->display_info.bpc = 8;
connector->display_info.width_mm = 154;
connector->display_info.height_mm = 86;
drm_display_info_set_bus_formats(&connector->display_info,
&bus_format, 1);
return num;
}
static const struct drm_panel_funcs rpi_touchscreen_funcs = {
.disable = rpi_touchscreen_disable,
.unprepare = rpi_touchscreen_noop,
.prepare = rpi_touchscreen_noop,
.enable = rpi_touchscreen_enable,
.get_modes = rpi_touchscreen_get_modes,
};
static int rpi_touchscreen_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct device *dev = &i2c->dev;
struct rpi_touchscreen *ts;
struct device_node *endpoint, *dsi_host_node;
struct mipi_dsi_host *host;
int ret, ver;
struct mipi_dsi_device_info info = {
.type = RPI_DSI_DRIVER_NAME,
.channel = 0,
.node = NULL,
};
ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
if (!ts)
return -ENOMEM;
i2c_set_clientdata(i2c, ts);
ts->i2c = i2c;
ver = rpi_touchscreen_i2c_read(ts, REG_ID);
if (ver < 0) {
dev_err(dev, "Atmel I2C read failed: %d\n", ver);
return -ENODEV;
}
switch (ver) {
case 0xde: /* ver 1 */
case 0xc3: /* ver 2 */
break;
default:
dev_err(dev, "Unknown Atmel firmware revision: 0x%02x\n", ver);
return -ENODEV;
}
/* Turn off at boot, so we can cleanly sequence powering on. */
rpi_touchscreen_i2c_write(ts, REG_POWERON, 0);
/* Look up the DSI host. It needs to probe before we do. */
endpoint = of_graph_get_next_endpoint(dev->of_node, NULL);
dsi_host_node = of_graph_get_remote_port_parent(endpoint);
host = of_find_mipi_dsi_host_by_node(dsi_host_node);
of_node_put(dsi_host_node);
if (!host) {
of_node_put(endpoint);
return -EPROBE_DEFER;
}
info.node = of_graph_get_remote_port(endpoint);
of_node_put(endpoint);
ts->dsi = mipi_dsi_device_register_full(host, &info);
if (IS_ERR(ts->dsi)) {
dev_err(dev, "DSI device registration failed: %ld\n",
PTR_ERR(ts->dsi));
return PTR_ERR(ts->dsi);
}
ts->base.dev = dev;
ts->base.funcs = &rpi_touchscreen_funcs;
/* This appears last, as it's what will unblock the DSI host
* driver's component bind function.
*/
ret = drm_panel_add(&ts->base);
if (ret)
return ret;
return 0;
}
static int rpi_touchscreen_remove(struct i2c_client *i2c)
{
struct rpi_touchscreen *ts = i2c_get_clientdata(i2c);
mipi_dsi_detach(ts->dsi);
drm_panel_remove(&ts->base);
mipi_dsi_device_unregister(ts->dsi);
kfree(ts->dsi);
return 0;
}
static int rpi_touchscreen_dsi_probe(struct mipi_dsi_device *dsi)
{
int ret;
dsi->mode_flags = (MIPI_DSI_MODE_VIDEO |
MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
MIPI_DSI_MODE_LPM);
dsi->format = MIPI_DSI_FMT_RGB888;
dsi->lanes = 1;
ret = mipi_dsi_attach(dsi);
if (ret)
dev_err(&dsi->dev, "failed to attach dsi to host: %d\n", ret);
return ret;
}
static struct mipi_dsi_driver rpi_touchscreen_dsi_driver = {
.driver.name = RPI_DSI_DRIVER_NAME,
.probe = rpi_touchscreen_dsi_probe,
};
static const struct of_device_id rpi_touchscreen_of_ids[] = {
{ .compatible = "raspberrypi,7inch-touchscreen-panel" },
{ } /* sentinel */
};
MODULE_DEVICE_TABLE(of, rpi_touchscreen_of_ids);
static struct i2c_driver rpi_touchscreen_driver = {
.driver = {
.name = "rpi_touchscreen",
.of_match_table = rpi_touchscreen_of_ids,
},
.probe = rpi_touchscreen_probe,
.remove = rpi_touchscreen_remove,
};
static int __init rpi_touchscreen_init(void)
{
mipi_dsi_driver_register(&rpi_touchscreen_dsi_driver);
return i2c_add_driver(&rpi_touchscreen_driver);
}
module_init(rpi_touchscreen_init);
static void __exit rpi_touchscreen_exit(void)
{
i2c_del_driver(&rpi_touchscreen_driver);
mipi_dsi_driver_unregister(&rpi_touchscreen_dsi_driver);
}
module_exit(rpi_touchscreen_exit);
MODULE_AUTHOR("Eric Anholt <eric@anholt.net>");
MODULE_DESCRIPTION("Raspberry Pi 7-inch touchscreen driver");
MODULE_LICENSE("GPL v2");
...@@ -791,9 +791,8 @@ static const struct drm_encoder_funcs ltdc_encoder_funcs = { ...@@ -791,9 +791,8 @@ static const struct drm_encoder_funcs ltdc_encoder_funcs = {
.destroy = drm_encoder_cleanup, .destroy = drm_encoder_cleanup,
}; };
static int ltdc_encoder_init(struct drm_device *ddev) static int ltdc_encoder_init(struct drm_device *ddev, struct drm_bridge *bridge)
{ {
struct ltdc_device *ldev = ddev->dev_private;
struct drm_encoder *encoder; struct drm_encoder *encoder;
int ret; int ret;
...@@ -807,7 +806,7 @@ static int ltdc_encoder_init(struct drm_device *ddev) ...@@ -807,7 +806,7 @@ static int ltdc_encoder_init(struct drm_device *ddev)
drm_encoder_init(ddev, encoder, &ltdc_encoder_funcs, drm_encoder_init(ddev, encoder, &ltdc_encoder_funcs,
DRM_MODE_ENCODER_DPI, NULL); DRM_MODE_ENCODER_DPI, NULL);
ret = drm_bridge_attach(encoder, ldev->bridge, NULL); ret = drm_bridge_attach(encoder, bridge, NULL);
if (ret) { if (ret) {
drm_encoder_cleanup(encoder); drm_encoder_cleanup(encoder);
return -EINVAL; return -EINVAL;
...@@ -936,12 +935,9 @@ int ltdc_load(struct drm_device *ddev) ...@@ -936,12 +935,9 @@ int ltdc_load(struct drm_device *ddev)
ret = PTR_ERR(bridge); ret = PTR_ERR(bridge);
goto err; goto err;
} }
ldev->is_panel_bridge = true;
} }
ldev->bridge = bridge; ret = ltdc_encoder_init(ddev, bridge);
ret = ltdc_encoder_init(ddev);
if (ret) { if (ret) {
DRM_ERROR("Failed to init encoder\n"); DRM_ERROR("Failed to init encoder\n");
goto err; goto err;
...@@ -972,8 +968,7 @@ int ltdc_load(struct drm_device *ddev) ...@@ -972,8 +968,7 @@ int ltdc_load(struct drm_device *ddev)
return 0; return 0;
err: err:
if (ldev->is_panel_bridge) drm_panel_bridge_remove(bridge);
drm_panel_bridge_remove(bridge);
clk_disable_unprepare(ldev->pixel_clk); clk_disable_unprepare(ldev->pixel_clk);
...@@ -986,8 +981,7 @@ void ltdc_unload(struct drm_device *ddev) ...@@ -986,8 +981,7 @@ void ltdc_unload(struct drm_device *ddev)
DRM_DEBUG_DRIVER("\n"); DRM_DEBUG_DRIVER("\n");
if (ldev->is_panel_bridge) drm_of_panel_bridge_remove(ddev->dev->of_node, 0, 0);
drm_panel_bridge_remove(ldev->bridge);
clk_disable_unprepare(ldev->pixel_clk); clk_disable_unprepare(ldev->pixel_clk);
} }
......
...@@ -24,8 +24,6 @@ struct ltdc_device { ...@@ -24,8 +24,6 @@ struct ltdc_device {
struct drm_fbdev_cma *fbdev; struct drm_fbdev_cma *fbdev;
void __iomem *regs; void __iomem *regs;
struct clk *pixel_clk; /* lcd pixel clock */ struct clk *pixel_clk; /* lcd pixel clock */
struct drm_bridge *bridge;
bool is_panel_bridge;
struct mutex err_lock; /* protecting error_status */ struct mutex err_lock; /* protecting error_status */
struct ltdc_caps caps; struct ltdc_caps caps;
u32 error_status; u32 error_status;
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include <drm/drm_connector.h> #include <drm/drm_connector.h>
#include <drm/drm_encoder.h> #include <drm/drm_encoder.h>
#include <linux/regmap.h>
#include <media/cec-pin.h> #include <media/cec-pin.h>
...@@ -58,10 +59,13 @@ ...@@ -58,10 +59,13 @@
#define SUN4I_HDMI_PAD_CTRL0_TXEN BIT(23) #define SUN4I_HDMI_PAD_CTRL0_TXEN BIT(23)
#define SUN4I_HDMI_PAD_CTRL1_REG 0x204 #define SUN4I_HDMI_PAD_CTRL1_REG 0x204
#define SUN4I_HDMI_PAD_CTRL1_UNKNOWN BIT(24) /* set on A31 */
#define SUN4I_HDMI_PAD_CTRL1_AMP_OPT BIT(23) #define SUN4I_HDMI_PAD_CTRL1_AMP_OPT BIT(23)
#define SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT BIT(22) #define SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT BIT(22)
#define SUN4I_HDMI_PAD_CTRL1_EMP_OPT BIT(20) #define SUN4I_HDMI_PAD_CTRL1_EMP_OPT BIT(20)
#define SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT BIT(19) #define SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT BIT(19)
#define SUN4I_HDMI_PAD_CTRL1_PWSCK BIT(18)
#define SUN4I_HDMI_PAD_CTRL1_PWSDT BIT(17)
#define SUN4I_HDMI_PAD_CTRL1_REG_DEN BIT(15) #define SUN4I_HDMI_PAD_CTRL1_REG_DEN BIT(15)
#define SUN4I_HDMI_PAD_CTRL1_REG_DENCK BIT(14) #define SUN4I_HDMI_PAD_CTRL1_REG_DENCK BIT(14)
#define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n) (((n) & 7) << 10) #define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n) (((n) & 7) << 10)
...@@ -152,21 +156,106 @@ ...@@ -152,21 +156,106 @@
#define SUN4I_HDMI_DDC_FIFO_SIZE 16 #define SUN4I_HDMI_DDC_FIFO_SIZE 16
/* A31 specific */
#define SUN6I_HDMI_DDC_CTRL_REG 0x500
#define SUN6I_HDMI_DDC_CTRL_RESET BIT(31)
#define SUN6I_HDMI_DDC_CTRL_START_CMD BIT(27)
#define SUN6I_HDMI_DDC_CTRL_SDA_ENABLE BIT(6)
#define SUN6I_HDMI_DDC_CTRL_SCL_ENABLE BIT(4)
#define SUN6I_HDMI_DDC_CTRL_ENABLE BIT(0)
#define SUN6I_HDMI_DDC_CMD_REG 0x508
#define SUN6I_HDMI_DDC_CMD_BYTE_COUNT(count) ((count) << 16)
/* command types in lower 3 bits are the same as sun4i */
#define SUN6I_HDMI_DDC_ADDR_REG 0x50c
#define SUN6I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) & 0xff) << 24)
#define SUN6I_HDMI_DDC_ADDR_EDDC(addr) (((addr) & 0xff) << 16)
#define SUN6I_HDMI_DDC_ADDR_OFFSET(off) (((off) & 0xff) << 8)
#define SUN6I_HDMI_DDC_ADDR_SLAVE(addr) (((addr) & 0xff) << 1)
#define SUN6I_HDMI_DDC_INT_STATUS_REG 0x514
#define SUN6I_HDMI_DDC_INT_STATUS_TIMEOUT BIT(8)
/* lower 8 bits are the same as sun4i */
#define SUN6I_HDMI_DDC_FIFO_CTRL_REG 0x518
#define SUN6I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(15)
/* lower 9 bits are the same as sun4i */
#define SUN6I_HDMI_DDC_CLK_REG 0x520
/* DDC CLK bit fields are the same, but the formula is not */
#define SUN6I_HDMI_DDC_FIFO_DATA_REG 0x580
enum sun4i_hdmi_pkt_type { enum sun4i_hdmi_pkt_type {
SUN4I_HDMI_PKT_AVI = 2, SUN4I_HDMI_PKT_AVI = 2,
SUN4I_HDMI_PKT_END = 15, SUN4I_HDMI_PKT_END = 15,
}; };
struct sun4i_hdmi_variant {
bool has_ddc_parent_clk;
bool has_reset_control;
u32 pad_ctrl0_init_val;
u32 pad_ctrl1_init_val;
u32 pll_ctrl_init_val;
struct reg_field ddc_clk_reg;
u8 ddc_clk_pre_divider;
u8 ddc_clk_m_offset;
u8 tmds_clk_div_offset;
/* Register fields for I2C adapter */
struct reg_field field_ddc_en;
struct reg_field field_ddc_start;
struct reg_field field_ddc_reset;
struct reg_field field_ddc_addr_reg;
struct reg_field field_ddc_slave_addr;
struct reg_field field_ddc_int_mask;
struct reg_field field_ddc_int_status;
struct reg_field field_ddc_fifo_clear;
struct reg_field field_ddc_fifo_rx_thres;
struct reg_field field_ddc_fifo_tx_thres;
struct reg_field field_ddc_byte_count;
struct reg_field field_ddc_cmd;
struct reg_field field_ddc_sda_en;
struct reg_field field_ddc_sck_en;
/* DDC FIFO register offset */
u32 ddc_fifo_reg;
/*
* DDC FIFO threshold boundary conditions
*
* This is used to cope with the threshold boundary condition
* being slightly different on sun5i and sun6i.
*
* On sun5i the threshold is exclusive, i.e. does not include,
* the value of the threshold. ( > for RX; < for TX )
* On sun6i the threshold is inclusive, i.e. includes, the
* value of the threshold. ( >= for RX; <= for TX )
*/
bool ddc_fifo_thres_incl;
bool ddc_fifo_has_dir;
};
struct sun4i_hdmi { struct sun4i_hdmi {
struct drm_connector connector; struct drm_connector connector;
struct drm_encoder encoder; struct drm_encoder encoder;
struct device *dev; struct device *dev;
void __iomem *base; void __iomem *base;
struct regmap *regmap;
/* Reset control */
struct reset_control *reset;
/* Parent clocks */ /* Parent clocks */
struct clk *bus_clk; struct clk *bus_clk;
struct clk *mod_clk; struct clk *mod_clk;
struct clk *ddc_parent_clk;
struct clk *pll0_clk; struct clk *pll0_clk;
struct clk *pll1_clk; struct clk *pll1_clk;
...@@ -176,10 +265,28 @@ struct sun4i_hdmi { ...@@ -176,10 +265,28 @@ struct sun4i_hdmi {
struct i2c_adapter *i2c; struct i2c_adapter *i2c;
/* Regmap fields for I2C adapter */
struct regmap_field *field_ddc_en;
struct regmap_field *field_ddc_start;
struct regmap_field *field_ddc_reset;
struct regmap_field *field_ddc_addr_reg;
struct regmap_field *field_ddc_slave_addr;
struct regmap_field *field_ddc_int_mask;
struct regmap_field *field_ddc_int_status;
struct regmap_field *field_ddc_fifo_clear;
struct regmap_field *field_ddc_fifo_rx_thres;
struct regmap_field *field_ddc_fifo_tx_thres;
struct regmap_field *field_ddc_byte_count;
struct regmap_field *field_ddc_cmd;
struct regmap_field *field_ddc_sda_en;
struct regmap_field *field_ddc_sck_en;
struct sun4i_drv *drv; struct sun4i_drv *drv;
bool hdmi_monitor; bool hdmi_monitor;
struct cec_adapter *cec_adap; struct cec_adapter *cec_adap;
const struct sun4i_hdmi_variant *variant;
}; };
int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk); int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
*/ */
#include <linux/clk-provider.h> #include <linux/clk-provider.h>
#include <linux/regmap.h>
#include "sun4i_tcon.h" #include "sun4i_tcon.h"
#include "sun4i_hdmi.h" #include "sun4i_hdmi.h"
...@@ -18,6 +19,9 @@ ...@@ -18,6 +19,9 @@
struct sun4i_ddc { struct sun4i_ddc {
struct clk_hw hw; struct clk_hw hw;
struct sun4i_hdmi *hdmi; struct sun4i_hdmi *hdmi;
struct regmap_field *reg;
u8 pre_div;
u8 m_offset;
}; };
static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw) static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
...@@ -27,6 +31,8 @@ static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw) ...@@ -27,6 +31,8 @@ static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
static unsigned long sun4i_ddc_calc_divider(unsigned long rate, static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
unsigned long parent_rate, unsigned long parent_rate,
const u8 pre_div,
const u8 m_offset,
u8 *m, u8 *n) u8 *m, u8 *n)
{ {
unsigned long best_rate = 0; unsigned long best_rate = 0;
...@@ -36,7 +42,8 @@ static unsigned long sun4i_ddc_calc_divider(unsigned long rate, ...@@ -36,7 +42,8 @@ static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
for (_n = 0; _n < 8; _n++) { for (_n = 0; _n < 8; _n++) {
unsigned long tmp_rate; unsigned long tmp_rate;
tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1); tmp_rate = (((parent_rate / pre_div) / 10) >> _n) /
(_m + m_offset);
if (tmp_rate > rate) if (tmp_rate > rate)
continue; continue;
...@@ -60,21 +67,25 @@ static unsigned long sun4i_ddc_calc_divider(unsigned long rate, ...@@ -60,21 +67,25 @@ static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate, static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate) unsigned long *prate)
{ {
return sun4i_ddc_calc_divider(rate, *prate, NULL, NULL); struct sun4i_ddc *ddc = hw_to_ddc(hw);
return sun4i_ddc_calc_divider(rate, *prate, ddc->pre_div,
ddc->m_offset, NULL, NULL);
} }
static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw, static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate) unsigned long parent_rate)
{ {
struct sun4i_ddc *ddc = hw_to_ddc(hw); struct sun4i_ddc *ddc = hw_to_ddc(hw);
u32 reg; unsigned int reg;
u8 m, n; u8 m, n;
reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG); regmap_field_read(ddc->reg, &reg);
m = (reg >> 3) & 0x7; m = (reg >> 3) & 0xf;
n = reg & 0x7; n = reg & 0x7;
return (((parent_rate / 2) / 10) >> n) / (m + 1); return (((parent_rate / ddc->pre_div) / 10) >> n) /
(m + ddc->m_offset);
} }
static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate, static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
...@@ -83,10 +94,12 @@ static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate, ...@@ -83,10 +94,12 @@ static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
struct sun4i_ddc *ddc = hw_to_ddc(hw); struct sun4i_ddc *ddc = hw_to_ddc(hw);
u8 div_m, div_n; u8 div_m, div_n;
sun4i_ddc_calc_divider(rate, parent_rate, &div_m, &div_n); sun4i_ddc_calc_divider(rate, parent_rate, ddc->pre_div,
ddc->m_offset, &div_m, &div_n);
writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n), regmap_field_write(ddc->reg,
ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG); SUN4I_HDMI_DDC_CLK_M(div_m) |
SUN4I_HDMI_DDC_CLK_N(div_n));
return 0; return 0;
} }
...@@ -111,6 +124,11 @@ int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent) ...@@ -111,6 +124,11 @@ int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
if (!ddc) if (!ddc)
return -ENOMEM; return -ENOMEM;
ddc->reg = devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
hdmi->variant->ddc_clk_reg);
if (IS_ERR(ddc->reg))
return PTR_ERR(ddc->reg);
init.name = "hdmi-ddc"; init.name = "hdmi-ddc";
init.ops = &sun4i_ddc_ops; init.ops = &sun4i_ddc_ops;
init.parent_names = &parent_name; init.parent_names = &parent_name;
...@@ -118,6 +136,8 @@ int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent) ...@@ -118,6 +136,8 @@ int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
ddc->hdmi = hdmi; ddc->hdmi = hdmi;
ddc->hw.init = &init; ddc->hw.init = &init;
ddc->pre_div = hdmi->variant->ddc_clk_pre_divider;
ddc->m_offset = hdmi->variant->ddc_clk_m_offset;
hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw); hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw);
if (IS_ERR(hdmi->ddc_clk)) if (IS_ERR(hdmi->ddc_clk))
......
...@@ -20,8 +20,11 @@ ...@@ -20,8 +20,11 @@
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/component.h> #include <linux/component.h>
#include <linux/iopoll.h> #include <linux/iopoll.h>
#include <linux/of_device.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/pm_runtime.h> #include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include "sun4i_backend.h" #include "sun4i_backend.h"
#include "sun4i_crtc.h" #include "sun4i_crtc.h"
...@@ -267,6 +270,124 @@ static const struct cec_pin_ops sun4i_hdmi_cec_pin_ops = { ...@@ -267,6 +270,124 @@ static const struct cec_pin_ops sun4i_hdmi_cec_pin_ops = {
}; };
#endif #endif
#define SUN4I_HDMI_PAD_CTRL1_MASK (GENMASK(24, 7) | GENMASK(5, 0))
#define SUN4I_HDMI_PLL_CTRL_MASK (GENMASK(31, 8) | GENMASK(3, 0))
static const struct sun4i_hdmi_variant sun5i_variant = {
.pad_ctrl0_init_val = SUN4I_HDMI_PAD_CTRL0_TXEN |
SUN4I_HDMI_PAD_CTRL0_CKEN |
SUN4I_HDMI_PAD_CTRL0_PWENG |
SUN4I_HDMI_PAD_CTRL0_PWEND |
SUN4I_HDMI_PAD_CTRL0_PWENC |
SUN4I_HDMI_PAD_CTRL0_LDODEN |
SUN4I_HDMI_PAD_CTRL0_LDOCEN |
SUN4I_HDMI_PAD_CTRL0_BIASEN,
.pad_ctrl1_init_val = SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) |
SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) |
SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
SUN4I_HDMI_PAD_CTRL1_REG_DEN |
SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
SUN4I_HDMI_PAD_CTRL1_AMP_OPT,
.pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) |
SUN4I_HDMI_PLL_CTRL_CS(7) |
SUN4I_HDMI_PLL_CTRL_CP_S(15) |
SUN4I_HDMI_PLL_CTRL_S(7) |
SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) |
SUN4I_HDMI_PLL_CTRL_SDIV2 |
SUN4I_HDMI_PLL_CTRL_LDO2_EN |
SUN4I_HDMI_PLL_CTRL_LDO1_EN |
SUN4I_HDMI_PLL_CTRL_HV_IS_33 |
SUN4I_HDMI_PLL_CTRL_BWS |
SUN4I_HDMI_PLL_CTRL_PLL_EN,
.ddc_clk_reg = REG_FIELD(SUN4I_HDMI_DDC_CLK_REG, 0, 6),
.ddc_clk_pre_divider = 2,
.ddc_clk_m_offset = 1,
.field_ddc_en = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 31, 31),
.field_ddc_start = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 30, 30),
.field_ddc_reset = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 0, 0),
.field_ddc_addr_reg = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 31),
.field_ddc_slave_addr = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 6),
.field_ddc_int_status = REG_FIELD(SUN4I_HDMI_DDC_INT_STATUS_REG, 0, 8),
.field_ddc_fifo_clear = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 31, 31),
.field_ddc_fifo_rx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 4, 7),
.field_ddc_fifo_tx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 0, 3),
.field_ddc_byte_count = REG_FIELD(SUN4I_HDMI_DDC_BYTE_COUNT_REG, 0, 9),
.field_ddc_cmd = REG_FIELD(SUN4I_HDMI_DDC_CMD_REG, 0, 2),
.field_ddc_sda_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 9, 9),
.field_ddc_sck_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 8, 8),
.ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG,
.ddc_fifo_has_dir = true,
};
static const struct sun4i_hdmi_variant sun6i_variant = {
.has_ddc_parent_clk = true,
.has_reset_control = true,
.pad_ctrl0_init_val = 0xff |
SUN4I_HDMI_PAD_CTRL0_TXEN |
SUN4I_HDMI_PAD_CTRL0_CKEN |
SUN4I_HDMI_PAD_CTRL0_PWENG |
SUN4I_HDMI_PAD_CTRL0_PWEND |
SUN4I_HDMI_PAD_CTRL0_PWENC |
SUN4I_HDMI_PAD_CTRL0_LDODEN |
SUN4I_HDMI_PAD_CTRL0_LDOCEN,
.pad_ctrl1_init_val = SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) |
SUN4I_HDMI_PAD_CTRL1_REG_EMP(4) |
SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
SUN4I_HDMI_PAD_CTRL1_REG_DEN |
SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
SUN4I_HDMI_PAD_CTRL1_PWSDT |
SUN4I_HDMI_PAD_CTRL1_PWSCK |
SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
SUN4I_HDMI_PAD_CTRL1_AMP_OPT |
SUN4I_HDMI_PAD_CTRL1_UNKNOWN,
.pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) |
SUN4I_HDMI_PLL_CTRL_CS(3) |
SUN4I_HDMI_PLL_CTRL_CP_S(10) |
SUN4I_HDMI_PLL_CTRL_S(4) |
SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) |
SUN4I_HDMI_PLL_CTRL_SDIV2 |
SUN4I_HDMI_PLL_CTRL_LDO2_EN |
SUN4I_HDMI_PLL_CTRL_LDO1_EN |
SUN4I_HDMI_PLL_CTRL_HV_IS_33 |
SUN4I_HDMI_PLL_CTRL_PLL_EN,
.ddc_clk_reg = REG_FIELD(SUN6I_HDMI_DDC_CLK_REG, 0, 6),
.ddc_clk_pre_divider = 1,
.ddc_clk_m_offset = 2,
.tmds_clk_div_offset = 1,
.field_ddc_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 0, 0),
.field_ddc_start = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 27, 27),
.field_ddc_reset = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 31, 31),
.field_ddc_addr_reg = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG, 1, 31),
.field_ddc_slave_addr = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG, 1, 7),
.field_ddc_int_status = REG_FIELD(SUN6I_HDMI_DDC_INT_STATUS_REG, 0, 8),
.field_ddc_fifo_clear = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 18, 18),
.field_ddc_fifo_rx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 4, 7),
.field_ddc_fifo_tx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 0, 3),
.field_ddc_byte_count = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG, 16, 25),
.field_ddc_cmd = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG, 0, 2),
.field_ddc_sda_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 6, 6),
.field_ddc_sck_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 4, 4),
.ddc_fifo_reg = SUN6I_HDMI_DDC_FIFO_DATA_REG,
.ddc_fifo_thres_incl = true,
};
static const struct regmap_config sun4i_hdmi_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = 0x580,
};
static int sun4i_hdmi_bind(struct device *dev, struct device *master, static int sun4i_hdmi_bind(struct device *dev, struct device *master,
void *data) void *data)
{ {
...@@ -285,6 +406,10 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, ...@@ -285,6 +406,10 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
hdmi->dev = dev; hdmi->dev = dev;
hdmi->drv = drv; hdmi->drv = drv;
hdmi->variant = of_device_get_match_data(dev);
if (!hdmi->variant)
return -EINVAL;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
hdmi->base = devm_ioremap_resource(dev, res); hdmi->base = devm_ioremap_resource(dev, res);
if (IS_ERR(hdmi->base)) { if (IS_ERR(hdmi->base)) {
...@@ -292,44 +417,76 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, ...@@ -292,44 +417,76 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
return PTR_ERR(hdmi->base); return PTR_ERR(hdmi->base);
} }
if (hdmi->variant->has_reset_control) {
hdmi->reset = devm_reset_control_get(dev, NULL);
if (IS_ERR(hdmi->reset)) {
dev_err(dev, "Couldn't get the HDMI reset control\n");
return PTR_ERR(hdmi->reset);
}
ret = reset_control_deassert(hdmi->reset);
if (ret) {
dev_err(dev, "Couldn't deassert HDMI reset\n");
return ret;
}
}
hdmi->bus_clk = devm_clk_get(dev, "ahb"); hdmi->bus_clk = devm_clk_get(dev, "ahb");
if (IS_ERR(hdmi->bus_clk)) { if (IS_ERR(hdmi->bus_clk)) {
dev_err(dev, "Couldn't get the HDMI bus clock\n"); dev_err(dev, "Couldn't get the HDMI bus clock\n");
return PTR_ERR(hdmi->bus_clk); ret = PTR_ERR(hdmi->bus_clk);
goto err_assert_reset;
} }
clk_prepare_enable(hdmi->bus_clk); clk_prepare_enable(hdmi->bus_clk);
hdmi->mod_clk = devm_clk_get(dev, "mod"); hdmi->mod_clk = devm_clk_get(dev, "mod");
if (IS_ERR(hdmi->mod_clk)) { if (IS_ERR(hdmi->mod_clk)) {
dev_err(dev, "Couldn't get the HDMI mod clock\n"); dev_err(dev, "Couldn't get the HDMI mod clock\n");
return PTR_ERR(hdmi->mod_clk); ret = PTR_ERR(hdmi->mod_clk);
goto err_disable_bus_clk;
} }
clk_prepare_enable(hdmi->mod_clk); clk_prepare_enable(hdmi->mod_clk);
hdmi->pll0_clk = devm_clk_get(dev, "pll-0"); hdmi->pll0_clk = devm_clk_get(dev, "pll-0");
if (IS_ERR(hdmi->pll0_clk)) { if (IS_ERR(hdmi->pll0_clk)) {
dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n"); dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n");
return PTR_ERR(hdmi->pll0_clk); ret = PTR_ERR(hdmi->pll0_clk);
goto err_disable_mod_clk;
} }
hdmi->pll1_clk = devm_clk_get(dev, "pll-1"); hdmi->pll1_clk = devm_clk_get(dev, "pll-1");
if (IS_ERR(hdmi->pll1_clk)) { if (IS_ERR(hdmi->pll1_clk)) {
dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n"); dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n");
return PTR_ERR(hdmi->pll1_clk); ret = PTR_ERR(hdmi->pll1_clk);
goto err_disable_mod_clk;
}
hdmi->regmap = devm_regmap_init_mmio(dev, hdmi->base,
&sun4i_hdmi_regmap_config);
if (IS_ERR(hdmi->regmap)) {
dev_err(dev, "Couldn't create HDMI encoder regmap\n");
return PTR_ERR(hdmi->regmap);
} }
ret = sun4i_tmds_create(hdmi); ret = sun4i_tmds_create(hdmi);
if (ret) { if (ret) {
dev_err(dev, "Couldn't create the TMDS clock\n"); dev_err(dev, "Couldn't create the TMDS clock\n");
return ret; goto err_disable_mod_clk;
}
if (hdmi->variant->has_ddc_parent_clk) {
hdmi->ddc_parent_clk = devm_clk_get(dev, "ddc");
if (IS_ERR(hdmi->ddc_parent_clk)) {
dev_err(dev, "Couldn't get the HDMI DDC clock\n");
return PTR_ERR(hdmi->ddc_parent_clk);
}
} else {
hdmi->ddc_parent_clk = hdmi->tmds_clk;
} }
writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG); writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG);
writel(SUN4I_HDMI_PAD_CTRL0_TXEN | SUN4I_HDMI_PAD_CTRL0_CKEN | writel(hdmi->variant->pad_ctrl0_init_val,
SUN4I_HDMI_PAD_CTRL0_PWENG | SUN4I_HDMI_PAD_CTRL0_PWEND |
SUN4I_HDMI_PAD_CTRL0_PWENC | SUN4I_HDMI_PAD_CTRL0_LDODEN |
SUN4I_HDMI_PAD_CTRL0_LDOCEN | SUN4I_HDMI_PAD_CTRL0_BIASEN,
hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG); hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG);
/* /*
...@@ -339,30 +496,18 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, ...@@ -339,30 +496,18 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
*/ */
reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
reg &= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK; reg &= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
reg |= SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) | reg |= hdmi->variant->pad_ctrl1_init_val;
SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) |
SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
SUN4I_HDMI_PAD_CTRL1_REG_DEN |
SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
SUN4I_HDMI_PAD_CTRL1_AMP_OPT;
writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
reg &= SUN4I_HDMI_PLL_CTRL_DIV_MASK; reg &= SUN4I_HDMI_PLL_CTRL_DIV_MASK;
reg |= SUN4I_HDMI_PLL_CTRL_VCO_S(8) | SUN4I_HDMI_PLL_CTRL_CS(7) | reg |= hdmi->variant->pll_ctrl_init_val;
SUN4I_HDMI_PLL_CTRL_CP_S(15) | SUN4I_HDMI_PLL_CTRL_S(7) |
SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | SUN4I_HDMI_PLL_CTRL_SDIV2 |
SUN4I_HDMI_PLL_CTRL_LDO2_EN | SUN4I_HDMI_PLL_CTRL_LDO1_EN |
SUN4I_HDMI_PLL_CTRL_HV_IS_33 | SUN4I_HDMI_PLL_CTRL_BWS |
SUN4I_HDMI_PLL_CTRL_PLL_EN;
writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
ret = sun4i_hdmi_i2c_create(dev, hdmi); ret = sun4i_hdmi_i2c_create(dev, hdmi);
if (ret) { if (ret) {
dev_err(dev, "Couldn't create the HDMI I2C adapter\n"); dev_err(dev, "Couldn't create the HDMI I2C adapter\n");
return ret; goto err_disable_mod_clk;
} }
drm_encoder_helper_add(&hdmi->encoder, drm_encoder_helper_add(&hdmi->encoder,
...@@ -422,6 +567,12 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, ...@@ -422,6 +567,12 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
drm_encoder_cleanup(&hdmi->encoder); drm_encoder_cleanup(&hdmi->encoder);
err_del_i2c_adapter: err_del_i2c_adapter:
i2c_del_adapter(hdmi->i2c); i2c_del_adapter(hdmi->i2c);
err_disable_mod_clk:
clk_disable_unprepare(hdmi->mod_clk);
err_disable_bus_clk:
clk_disable_unprepare(hdmi->bus_clk);
err_assert_reset:
reset_control_assert(hdmi->reset);
return ret; return ret;
} }
...@@ -434,6 +585,8 @@ static void sun4i_hdmi_unbind(struct device *dev, struct device *master, ...@@ -434,6 +585,8 @@ static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
drm_connector_cleanup(&hdmi->connector); drm_connector_cleanup(&hdmi->connector);
drm_encoder_cleanup(&hdmi->encoder); drm_encoder_cleanup(&hdmi->encoder);
i2c_del_adapter(hdmi->i2c); i2c_del_adapter(hdmi->i2c);
clk_disable_unprepare(hdmi->mod_clk);
clk_disable_unprepare(hdmi->bus_clk);
} }
static const struct component_ops sun4i_hdmi_ops = { static const struct component_ops sun4i_hdmi_ops = {
...@@ -454,7 +607,8 @@ static int sun4i_hdmi_remove(struct platform_device *pdev) ...@@ -454,7 +607,8 @@ static int sun4i_hdmi_remove(struct platform_device *pdev)
} }
static const struct of_device_id sun4i_hdmi_of_table[] = { static const struct of_device_id sun4i_hdmi_of_table[] = {
{ .compatible = "allwinner,sun5i-a10s-hdmi" }, { .compatible = "allwinner,sun5i-a10s-hdmi", .data = &sun5i_variant, },
{ .compatible = "allwinner,sun6i-a31-hdmi", .data = &sun6i_variant, },
{ } { }
}; };
MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table); MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table);
......
...@@ -25,8 +25,6 @@ ...@@ -25,8 +25,6 @@
/* FIFO request bit is set when FIFO level is above RX_THRESHOLD during read */ /* FIFO request bit is set when FIFO level is above RX_THRESHOLD during read */
#define RX_THRESHOLD SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX #define RX_THRESHOLD SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX
/* FIFO request bit is set when FIFO level is below TX_THRESHOLD during write */
#define TX_THRESHOLD 1
static int fifo_transfer(struct sun4i_hdmi *hdmi, u8 *buf, int len, bool read) static int fifo_transfer(struct sun4i_hdmi *hdmi, u8 *buf, int len, bool read)
{ {
...@@ -39,27 +37,36 @@ static int fifo_transfer(struct sun4i_hdmi *hdmi, u8 *buf, int len, bool read) ...@@ -39,27 +37,36 @@ static int fifo_transfer(struct sun4i_hdmi *hdmi, u8 *buf, int len, bool read)
SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST | SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST |
SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE; SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE;
u32 reg; u32 reg;
/*
* If threshold is inclusive, then the FIFO may only have
* RX_THRESHOLD number of bytes, instead of RX_THRESHOLD + 1.
*/
int read_len = RX_THRESHOLD +
(hdmi->variant->ddc_fifo_thres_incl ? 0 : 1);
/* Limit transfer length by FIFO threshold */ /*
len = min_t(int, len, read ? (RX_THRESHOLD + 1) : * Limit transfer length by FIFO threshold or FIFO size.
(SUN4I_HDMI_DDC_FIFO_SIZE - TX_THRESHOLD + 1)); * For TX the threshold is for an empty FIFO.
*/
len = min_t(int, len, read ? read_len : SUN4I_HDMI_DDC_FIFO_SIZE);
/* Wait until error, FIFO request bit set or transfer complete */ /* Wait until error, FIFO request bit set or transfer complete */
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG, reg, if (regmap_field_read_poll_timeout(hdmi->field_ddc_int_status, reg,
reg & mask, len * byte_time_ns, 100000)) reg & mask, len * byte_time_ns,
100000))
return -ETIMEDOUT; return -ETIMEDOUT;
if (reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) if (reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK)
return -EIO; return -EIO;
if (read) if (read)
readsb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG, buf, len); readsb(hdmi->base + hdmi->variant->ddc_fifo_reg, buf, len);
else else
writesb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG, buf, len); writesb(hdmi->base + hdmi->variant->ddc_fifo_reg, buf, len);
/* Clear FIFO request bit */ /* Clear FIFO request bit by forcing a write to that bit */
writel(SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST, regmap_field_force_write(hdmi->field_ddc_int_status,
hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG); SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST);
return len; return len;
} }
...@@ -70,50 +77,52 @@ static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg) ...@@ -70,50 +77,52 @@ static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg)
u32 reg; u32 reg;
/* Set FIFO direction */ /* Set FIFO direction */
reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); if (hdmi->variant->ddc_fifo_has_dir) {
reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK; reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
reg |= (msg->flags & I2C_M_RD) ? reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ : reg |= (msg->flags & I2C_M_RD) ?
SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE; SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ :
writel(reg, hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE;
writel(reg, hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
}
/* Clear address register (not cleared by soft reset) */
regmap_field_write(hdmi->field_ddc_addr_reg, 0);
/* Set I2C address */ /* Set I2C address */
writel(SUN4I_HDMI_DDC_ADDR_SLAVE(msg->addr), regmap_field_write(hdmi->field_ddc_slave_addr, msg->addr);
hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
/*
/* Set FIFO RX/TX thresholds and clear FIFO */ * Set FIFO RX/TX thresholds and clear FIFO
reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG); *
reg |= SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR; * If threshold is inclusive, we can set the TX threshold to
reg &= ~SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK; * 0 instead of 1.
reg |= SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES(RX_THRESHOLD); */
reg &= ~SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK; regmap_field_write(hdmi->field_ddc_fifo_tx_thres,
reg |= SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES(TX_THRESHOLD); hdmi->variant->ddc_fifo_thres_incl ? 0 : 1);
writel(reg, hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG); regmap_field_write(hdmi->field_ddc_fifo_rx_thres, RX_THRESHOLD);
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG, regmap_field_write(hdmi->field_ddc_fifo_clear, 1);
reg, if (regmap_field_read_poll_timeout(hdmi->field_ddc_fifo_clear,
!(reg & SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR), reg, !reg, 100, 2000))
100, 2000))
return -EIO; return -EIO;
/* Set transfer length */ /* Set transfer length */
writel(msg->len, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG); regmap_field_write(hdmi->field_ddc_byte_count, msg->len);
/* Set command */ /* Set command */
writel(msg->flags & I2C_M_RD ? regmap_field_write(hdmi->field_ddc_cmd,
SUN4I_HDMI_DDC_CMD_IMPLICIT_READ : msg->flags & I2C_M_RD ?
SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE, SUN4I_HDMI_DDC_CMD_IMPLICIT_READ :
hdmi->base + SUN4I_HDMI_DDC_CMD_REG); SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE);
/* Clear interrupt status bits */ /* Clear interrupt status bits by forcing a write */
writel(SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK | regmap_field_force_write(hdmi->field_ddc_int_status,
SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST | SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK |
SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE, SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST |
hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG); SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE);
/* Start command */ /* Start command */
reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); regmap_field_write(hdmi->field_ddc_start, 1);
writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
/* Transfer bytes */ /* Transfer bytes */
for (i = 0; i < msg->len; i += len) { for (i = 0; i < msg->len; i += len) {
...@@ -124,14 +133,12 @@ static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg) ...@@ -124,14 +133,12 @@ static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg)
} }
/* Wait for command to finish */ /* Wait for command to finish */
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, if (regmap_field_read_poll_timeout(hdmi->field_ddc_start,
reg, reg, !reg, 100, 100000))
!(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
100, 100000))
return -EIO; return -EIO;
/* Check for errors */ /* Check for errors */
reg = readl(hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG); regmap_field_read(hdmi->field_ddc_int_status, &reg);
if ((reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) || if ((reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) ||
!(reg & SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE)) { !(reg & SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE)) {
return -EIO; return -EIO;
...@@ -154,20 +161,21 @@ static int sun4i_hdmi_i2c_xfer(struct i2c_adapter *adap, ...@@ -154,20 +161,21 @@ static int sun4i_hdmi_i2c_xfer(struct i2c_adapter *adap,
return -EINVAL; return -EINVAL;
} }
/* DDC clock needs to be enabled for the module to work */
clk_prepare_enable(hdmi->ddc_clk);
clk_set_rate(hdmi->ddc_clk, 100000);
/* Reset I2C controller */ /* Reset I2C controller */
writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET, regmap_field_write(hdmi->field_ddc_en, 1);
hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); regmap_field_write(hdmi->field_ddc_reset, 1);
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg, if (regmap_field_read_poll_timeout(hdmi->field_ddc_reset,
!(reg & SUN4I_HDMI_DDC_CTRL_RESET), reg, !reg, 100, 2000)) {
100, 2000)) clk_disable_unprepare(hdmi->ddc_clk);
return -EIO; return -EIO;
}
writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE | regmap_field_write(hdmi->field_ddc_sck_en, 1);
SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE, regmap_field_write(hdmi->field_ddc_sda_en, 1);
hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
clk_prepare_enable(hdmi->ddc_clk);
clk_set_rate(hdmi->ddc_clk, 100000);
for (i = 0; i < num; i++) { for (i = 0; i < num; i++) {
err = xfer_msg(hdmi, &msgs[i]); err = xfer_msg(hdmi, &msgs[i]);
...@@ -191,12 +199,105 @@ static const struct i2c_algorithm sun4i_hdmi_i2c_algorithm = { ...@@ -191,12 +199,105 @@ static const struct i2c_algorithm sun4i_hdmi_i2c_algorithm = {
.functionality = sun4i_hdmi_i2c_func, .functionality = sun4i_hdmi_i2c_func,
}; };
static int sun4i_hdmi_init_regmap_fields(struct sun4i_hdmi *hdmi)
{
hdmi->field_ddc_en =
devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
hdmi->variant->field_ddc_en);
if (IS_ERR(hdmi->field_ddc_en))
return PTR_ERR(hdmi->field_ddc_en);
hdmi->field_ddc_start =
devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
hdmi->variant->field_ddc_start);
if (IS_ERR(hdmi->field_ddc_start))
return PTR_ERR(hdmi->field_ddc_start);
hdmi->field_ddc_reset =
devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
hdmi->variant->field_ddc_reset);
if (IS_ERR(hdmi->field_ddc_reset))
return PTR_ERR(hdmi->field_ddc_reset);
hdmi->field_ddc_addr_reg =
devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
hdmi->variant->field_ddc_addr_reg);
if (IS_ERR(hdmi->field_ddc_addr_reg))
return PTR_ERR(hdmi->field_ddc_addr_reg);
hdmi->field_ddc_slave_addr =
devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
hdmi->variant->field_ddc_slave_addr);
if (IS_ERR(hdmi->field_ddc_slave_addr))
return PTR_ERR(hdmi->field_ddc_slave_addr);
hdmi->field_ddc_int_mask =
devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
hdmi->variant->field_ddc_int_mask);
if (IS_ERR(hdmi->field_ddc_int_mask))
return PTR_ERR(hdmi->field_ddc_int_mask);
hdmi->field_ddc_int_status =
devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
hdmi->variant->field_ddc_int_status);
if (IS_ERR(hdmi->field_ddc_int_status))
return PTR_ERR(hdmi->field_ddc_int_status);
hdmi->field_ddc_fifo_clear =
devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
hdmi->variant->field_ddc_fifo_clear);
if (IS_ERR(hdmi->field_ddc_fifo_clear))
return PTR_ERR(hdmi->field_ddc_fifo_clear);
hdmi->field_ddc_fifo_rx_thres =
devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
hdmi->variant->field_ddc_fifo_rx_thres);
if (IS_ERR(hdmi->field_ddc_fifo_rx_thres))
return PTR_ERR(hdmi->field_ddc_fifo_rx_thres);
hdmi->field_ddc_fifo_tx_thres =
devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
hdmi->variant->field_ddc_fifo_tx_thres);
if (IS_ERR(hdmi->field_ddc_fifo_tx_thres))
return PTR_ERR(hdmi->field_ddc_fifo_tx_thres);
hdmi->field_ddc_byte_count =
devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
hdmi->variant->field_ddc_byte_count);
if (IS_ERR(hdmi->field_ddc_byte_count))
return PTR_ERR(hdmi->field_ddc_byte_count);
hdmi->field_ddc_cmd =
devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
hdmi->variant->field_ddc_cmd);
if (IS_ERR(hdmi->field_ddc_cmd))
return PTR_ERR(hdmi->field_ddc_cmd);
hdmi->field_ddc_sda_en =
devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
hdmi->variant->field_ddc_sda_en);
if (IS_ERR(hdmi->field_ddc_sda_en))
return PTR_ERR(hdmi->field_ddc_sda_en);
hdmi->field_ddc_sck_en =
devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
hdmi->variant->field_ddc_sck_en);
if (IS_ERR(hdmi->field_ddc_sck_en))
return PTR_ERR(hdmi->field_ddc_sck_en);
return 0;
}
int sun4i_hdmi_i2c_create(struct device *dev, struct sun4i_hdmi *hdmi) int sun4i_hdmi_i2c_create(struct device *dev, struct sun4i_hdmi *hdmi)
{ {
struct i2c_adapter *adap; struct i2c_adapter *adap;
int ret = 0; int ret = 0;
ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk); ret = sun4i_ddc_create(hdmi, hdmi->ddc_parent_clk);
if (ret)
return ret;
ret = sun4i_hdmi_init_regmap_fields(hdmi);
if (ret) if (ret)
return ret; return ret;
......
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
struct sun4i_tmds { struct sun4i_tmds {
struct clk_hw hw; struct clk_hw hw;
struct sun4i_hdmi *hdmi; struct sun4i_hdmi *hdmi;
u8 div_offset;
}; };
static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw) static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
...@@ -28,6 +30,7 @@ static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw) ...@@ -28,6 +30,7 @@ static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
static unsigned long sun4i_tmds_calc_divider(unsigned long rate, static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
unsigned long parent_rate, unsigned long parent_rate,
u8 div_offset,
u8 *div, u8 *div,
bool *half) bool *half)
{ {
...@@ -35,7 +38,7 @@ static unsigned long sun4i_tmds_calc_divider(unsigned long rate, ...@@ -35,7 +38,7 @@ static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
u8 best_m = 0, m; u8 best_m = 0, m;
bool is_double; bool is_double;
for (m = 1; m < 16; m++) { for (m = div_offset ?: 1; m < (16 + div_offset); m++) {
u8 d; u8 d;
for (d = 1; d < 3; d++) { for (d = 1; d < 3; d++) {
...@@ -67,11 +70,12 @@ static unsigned long sun4i_tmds_calc_divider(unsigned long rate, ...@@ -67,11 +70,12 @@ static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
static int sun4i_tmds_determine_rate(struct clk_hw *hw, static int sun4i_tmds_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req) struct clk_rate_request *req)
{ {
struct clk_hw *parent; struct sun4i_tmds *tmds = hw_to_tmds(hw);
struct clk_hw *parent = NULL;
unsigned long best_parent = 0; unsigned long best_parent = 0;
unsigned long rate = req->rate; unsigned long rate = req->rate;
int best_div = 1, best_half = 1; int best_div = 1, best_half = 1;
int i, j; int i, j, p;
/* /*
* We only consider PLL3, since the TCON is very likely to be * We only consider PLL3, since the TCON is very likely to be
...@@ -79,32 +83,38 @@ static int sun4i_tmds_determine_rate(struct clk_hw *hw, ...@@ -79,32 +83,38 @@ static int sun4i_tmds_determine_rate(struct clk_hw *hw,
* clock, so we should not need to do anything. * clock, so we should not need to do anything.
*/ */
parent = clk_hw_get_parent_by_index(hw, 0); for (p = 0; p < clk_hw_get_num_parents(hw); p++) {
if (!parent) parent = clk_hw_get_parent_by_index(hw, p);
return -EINVAL; if (!parent)
continue;
for (i = 1; i < 3; i++) {
for (j = 1; j < 16; j++) { for (i = 1; i < 3; i++) {
unsigned long ideal = rate * i * j; for (j = tmds->div_offset ?: 1;
unsigned long rounded; j < (16 + tmds->div_offset); j++) {
unsigned long ideal = rate * i * j;
rounded = clk_hw_round_rate(parent, ideal); unsigned long rounded;
if (rounded == ideal) { rounded = clk_hw_round_rate(parent, ideal);
best_parent = rounded;
best_half = i; if (rounded == ideal) {
best_div = j; best_parent = rounded;
goto out; best_half = i;
} best_div = j;
goto out;
if (abs(rate - rounded / i) < }
abs(rate - best_parent / best_div)) {
best_parent = rounded; if (abs(rate - rounded / i) <
best_div = i; abs(rate - best_parent / best_div)) {
best_parent = rounded;
best_div = i;
}
} }
} }
} }
if (!parent)
return -EINVAL;
out: out:
req->rate = best_parent / best_half / best_div; req->rate = best_parent / best_half / best_div;
req->best_parent_rate = best_parent; req->best_parent_rate = best_parent;
...@@ -124,7 +134,7 @@ static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw, ...@@ -124,7 +134,7 @@ static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
parent_rate /= 2; parent_rate /= 2;
reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
reg = (reg >> 4) & 0xf; reg = ((reg >> 4) & 0xf) + tmds->div_offset;
if (!reg) if (!reg)
reg = 1; reg = 1;
...@@ -139,7 +149,8 @@ static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate, ...@@ -139,7 +149,8 @@ static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
u32 reg; u32 reg;
u8 div; u8 div;
sun4i_tmds_calc_divider(rate, parent_rate, &div, &half); sun4i_tmds_calc_divider(rate, parent_rate, tmds->div_offset,
&div, &half);
reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK; reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
...@@ -149,7 +160,7 @@ static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate, ...@@ -149,7 +160,7 @@ static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK; reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK;
writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div), writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div - tmds->div_offset),
tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
return 0; return 0;
...@@ -216,6 +227,7 @@ int sun4i_tmds_create(struct sun4i_hdmi *hdmi) ...@@ -216,6 +227,7 @@ int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
tmds->hdmi = hdmi; tmds->hdmi = hdmi;
tmds->hw.init = &init; tmds->hw.init = &init;
tmds->div_offset = hdmi->variant->tmds_clk_div_offset;
hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw); hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
if (IS_ERR(hdmi->tmds_clk)) if (IS_ERR(hdmi->tmds_clk))
......
...@@ -14,9 +14,12 @@ ...@@ -14,9 +14,12 @@
#include <drm/drm_atomic_helper.h> #include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h> #include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h> #include <drm/drm_crtc_helper.h>
#include <drm/drm_encoder.h>
#include <drm/drm_modes.h> #include <drm/drm_modes.h>
#include <drm/drm_of.h> #include <drm/drm_of.h>
#include <uapi/drm/drm_mode.h>
#include <linux/component.h> #include <linux/component.h>
#include <linux/ioport.h> #include <linux/ioport.h>
#include <linux/of_address.h> #include <linux/of_address.h>
...@@ -109,26 +112,37 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable) ...@@ -109,26 +112,37 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
} }
EXPORT_SYMBOL(sun4i_tcon_enable_vblank); EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, /*
struct drm_encoder *encoder) * This function is a helper for TCON output muxing. The TCON output
* muxing control register in earlier SoCs (without the TCON TOP block)
* are located in TCON0. This helper returns a pointer to TCON0's
* sun4i_tcon structure, or NULL if not found.
*/
static struct sun4i_tcon *sun4i_get_tcon0(struct drm_device *drm)
{ {
u32 val; struct sun4i_drv *drv = drm->dev_private;
struct sun4i_tcon *tcon;
if (!tcon->quirks->has_unknown_mux) list_for_each_entry(tcon, &drv->tcon_list, list)
return; if (tcon->id == 0)
return tcon;
if (channel != 1) dev_warn(drm->dev,
return; "TCON0 not found, display output muxing may not work\n");
if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC) return NULL;
val = 1; }
else
val = 0;
/* void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
* FIXME: Undocumented bits struct drm_encoder *encoder)
*/ {
regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val); int ret = -ENOTSUPP;
if (tcon->quirks->set_mux)
ret = tcon->quirks->set_mux(tcon, encoder);
DRM_DEBUG_DRIVER("Muxing encoder %s to CRTC %s: %d\n",
encoder->name, encoder->crtc->name, ret);
} }
EXPORT_SYMBOL(sun4i_tcon_set_mux); EXPORT_SYMBOL(sun4i_tcon_set_mux);
...@@ -767,14 +781,57 @@ static int sun4i_tcon_remove(struct platform_device *pdev) ...@@ -767,14 +781,57 @@ static int sun4i_tcon_remove(struct platform_device *pdev)
return 0; return 0;
} }
/* platform specific TCON muxing callbacks */
static int sun5i_a13_tcon_set_mux(struct sun4i_tcon *tcon,
struct drm_encoder *encoder)
{
u32 val;
if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC)
val = 1;
else
val = 0;
/*
* FIXME: Undocumented bits
*/
return regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val);
}
static int sun6i_tcon_set_mux(struct sun4i_tcon *tcon,
struct drm_encoder *encoder)
{
struct sun4i_tcon *tcon0 = sun4i_get_tcon0(encoder->dev);
u32 shift;
if (!tcon0)
return -EINVAL;
switch (encoder->encoder_type) {
case DRM_MODE_ENCODER_TMDS:
/* HDMI */
shift = 8;
break;
default:
/* TODO A31 has MIPI DSI but A31s does not */
return -EINVAL;
}
regmap_update_bits(tcon0->regs, SUN4I_TCON_MUX_CTRL_REG,
0x3 << shift, tcon->id << shift);
return 0;
}
static const struct sun4i_tcon_quirks sun5i_a13_quirks = { static const struct sun4i_tcon_quirks sun5i_a13_quirks = {
.has_unknown_mux = true, .has_channel_1 = true,
.has_channel_1 = true, .set_mux = sun5i_a13_tcon_set_mux,
}; };
static const struct sun4i_tcon_quirks sun6i_a31_quirks = { static const struct sun4i_tcon_quirks sun6i_a31_quirks = {
.has_channel_1 = true, .has_channel_1 = true,
.needs_de_be_mux = true, .needs_de_be_mux = true,
.set_mux = sun6i_tcon_set_mux,
}; };
static const struct sun4i_tcon_quirks sun6i_a31s_quirks = { static const struct sun4i_tcon_quirks sun6i_a31s_quirks = {
......
...@@ -145,10 +145,14 @@ ...@@ -145,10 +145,14 @@
#define SUN4I_TCON_MAX_CHANNELS 2 #define SUN4I_TCON_MAX_CHANNELS 2
struct sun4i_tcon;
struct sun4i_tcon_quirks { struct sun4i_tcon_quirks {
bool has_unknown_mux; /* sun5i has undocumented mux */
bool has_channel_1; /* a33 does not have channel 1 */ bool has_channel_1; /* a33 does not have channel 1 */
bool needs_de_be_mux; /* sun6i needs mux to select backend */ bool needs_de_be_mux; /* sun6i needs mux to select backend */
/* callback to handle tcon muxing options */
int (*set_mux)(struct sun4i_tcon *, struct drm_encoder *);
}; };
struct sun4i_tcon { struct sun4i_tcon {
......
...@@ -97,8 +97,6 @@ struct vc4_dpi { ...@@ -97,8 +97,6 @@ struct vc4_dpi {
struct drm_encoder *encoder; struct drm_encoder *encoder;
struct drm_connector *connector; struct drm_connector *connector;
struct drm_bridge *bridge;
bool is_panel_bridge;
void __iomem *regs; void __iomem *regs;
...@@ -251,10 +249,11 @@ static int vc4_dpi_init_bridge(struct vc4_dpi *dpi) ...@@ -251,10 +249,11 @@ static int vc4_dpi_init_bridge(struct vc4_dpi *dpi)
{ {
struct device *dev = &dpi->pdev->dev; struct device *dev = &dpi->pdev->dev;
struct drm_panel *panel; struct drm_panel *panel;
struct drm_bridge *bridge;
int ret; int ret;
ret = drm_of_find_panel_or_bridge(dev->of_node, 0, 0, ret = drm_of_find_panel_or_bridge(dev->of_node, 0, 0,
&panel, &dpi->bridge); &panel, &bridge);
if (ret) { if (ret) {
/* If nothing was connected in the DT, that's not an /* If nothing was connected in the DT, that's not an
* error. * error.
...@@ -265,13 +264,10 @@ static int vc4_dpi_init_bridge(struct vc4_dpi *dpi) ...@@ -265,13 +264,10 @@ static int vc4_dpi_init_bridge(struct vc4_dpi *dpi)
return ret; return ret;
} }
if (panel) { if (panel)
dpi->bridge = drm_panel_bridge_add(panel, bridge = drm_panel_bridge_add(panel, DRM_MODE_CONNECTOR_DPI);
DRM_MODE_CONNECTOR_DPI);
dpi->is_panel_bridge = true;
}
return drm_bridge_attach(dpi->encoder, dpi->bridge, NULL); return drm_bridge_attach(dpi->encoder, bridge, NULL);
} }
static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) static int vc4_dpi_bind(struct device *dev, struct device *master, void *data)
...@@ -352,8 +348,7 @@ static void vc4_dpi_unbind(struct device *dev, struct device *master, ...@@ -352,8 +348,7 @@ static void vc4_dpi_unbind(struct device *dev, struct device *master,
struct vc4_dev *vc4 = to_vc4_dev(drm); struct vc4_dev *vc4 = to_vc4_dev(drm);
struct vc4_dpi *dpi = dev_get_drvdata(dev); struct vc4_dpi *dpi = dev_get_drvdata(dev);
if (dpi->is_panel_bridge) drm_of_panel_bridge_remove(dev->of_node, 0, 0);
drm_panel_bridge_remove(dpi->bridge);
drm_encoder_cleanup(dpi->encoder); drm_encoder_cleanup(dpi->encoder);
......
...@@ -262,6 +262,10 @@ enum { ...@@ -262,6 +262,10 @@ enum {
#define MHL_RAPK_UNSUPPORTED 0x02 /* Rcvd RAP action code not supported */ #define MHL_RAPK_UNSUPPORTED 0x02 /* Rcvd RAP action code not supported */
#define MHL_RAPK_BUSY 0x03 /* Responder too busy to respond */ #define MHL_RAPK_BUSY 0x03 /* Responder too busy to respond */
/* Bit masks for RCP messages */
#define MHL_RCP_KEY_RELEASED_MASK 0x80
#define MHL_RCP_KEY_ID_MASK 0x7F
/* /*
* Error status codes for RCPE messages * Error status codes for RCPE messages
*/ */
......
...@@ -585,12 +585,12 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p); ...@@ -585,12 +585,12 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
*/ */
#define for_each_oldnew_connector_in_state(__state, connector, old_connector_state, new_connector_state, __i) \ #define for_each_oldnew_connector_in_state(__state, connector, old_connector_state, new_connector_state, __i) \
for ((__i) = 0; \ for ((__i) = 0; \
(__i) < (__state)->num_connector && \ (__i) < (__state)->num_connector; \
((connector) = (__state)->connectors[__i].ptr, \ (__i)++) \
(old_connector_state) = (__state)->connectors[__i].old_state, \ for_each_if ((__state)->connectors[__i].ptr && \
(new_connector_state) = (__state)->connectors[__i].new_state, 1); \ ((connector) = (__state)->connectors[__i].ptr, \
(__i)++) \ (old_connector_state) = (__state)->connectors[__i].old_state, \
for_each_if (connector) (new_connector_state) = (__state)->connectors[__i].new_state, 1))
/** /**
* for_each_old_connector_in_state - iterate over all connectors in an atomic update * for_each_old_connector_in_state - iterate over all connectors in an atomic update
...@@ -606,11 +606,11 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p); ...@@ -606,11 +606,11 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
*/ */
#define for_each_old_connector_in_state(__state, connector, old_connector_state, __i) \ #define for_each_old_connector_in_state(__state, connector, old_connector_state, __i) \
for ((__i) = 0; \ for ((__i) = 0; \
(__i) < (__state)->num_connector && \ (__i) < (__state)->num_connector; \
((connector) = (__state)->connectors[__i].ptr, \ (__i)++) \
(old_connector_state) = (__state)->connectors[__i].old_state, 1); \ for_each_if ((__state)->connectors[__i].ptr && \
(__i)++) \ ((connector) = (__state)->connectors[__i].ptr, \
for_each_if (connector) (old_connector_state) = (__state)->connectors[__i].old_state, 1))
/** /**
* for_each_new_connector_in_state - iterate over all connectors in an atomic update * for_each_new_connector_in_state - iterate over all connectors in an atomic update
...@@ -626,11 +626,11 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p); ...@@ -626,11 +626,11 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
*/ */
#define for_each_new_connector_in_state(__state, connector, new_connector_state, __i) \ #define for_each_new_connector_in_state(__state, connector, new_connector_state, __i) \
for ((__i) = 0; \ for ((__i) = 0; \
(__i) < (__state)->num_connector && \ (__i) < (__state)->num_connector; \
((connector) = (__state)->connectors[__i].ptr, \ (__i)++) \
(new_connector_state) = (__state)->connectors[__i].new_state, 1); \ for_each_if ((__state)->connectors[__i].ptr && \
(__i)++) \ ((connector) = (__state)->connectors[__i].ptr, \
for_each_if (connector) (new_connector_state) = (__state)->connectors[__i].new_state, 1))
/** /**
* for_each_oldnew_crtc_in_state - iterate over all CRTCs in an atomic update * for_each_oldnew_crtc_in_state - iterate over all CRTCs in an atomic update
...@@ -646,12 +646,12 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p); ...@@ -646,12 +646,12 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
*/ */
#define for_each_oldnew_crtc_in_state(__state, crtc, old_crtc_state, new_crtc_state, __i) \ #define for_each_oldnew_crtc_in_state(__state, crtc, old_crtc_state, new_crtc_state, __i) \
for ((__i) = 0; \ for ((__i) = 0; \
(__i) < (__state)->dev->mode_config.num_crtc && \ (__i) < (__state)->dev->mode_config.num_crtc; \
((crtc) = (__state)->crtcs[__i].ptr, \
(old_crtc_state) = (__state)->crtcs[__i].old_state, \
(new_crtc_state) = (__state)->crtcs[__i].new_state, 1); \
(__i)++) \ (__i)++) \
for_each_if (crtc) for_each_if ((__state)->crtcs[__i].ptr && \
((crtc) = (__state)->crtcs[__i].ptr, \
(old_crtc_state) = (__state)->crtcs[__i].old_state, \
(new_crtc_state) = (__state)->crtcs[__i].new_state, 1))
/** /**
* for_each_old_crtc_in_state - iterate over all CRTCs in an atomic update * for_each_old_crtc_in_state - iterate over all CRTCs in an atomic update
...@@ -666,11 +666,11 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p); ...@@ -666,11 +666,11 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
*/ */
#define for_each_old_crtc_in_state(__state, crtc, old_crtc_state, __i) \ #define for_each_old_crtc_in_state(__state, crtc, old_crtc_state, __i) \
for ((__i) = 0; \ for ((__i) = 0; \
(__i) < (__state)->dev->mode_config.num_crtc && \ (__i) < (__state)->dev->mode_config.num_crtc; \
((crtc) = (__state)->crtcs[__i].ptr, \
(old_crtc_state) = (__state)->crtcs[__i].old_state, 1); \
(__i)++) \ (__i)++) \
for_each_if (crtc) for_each_if ((__state)->crtcs[__i].ptr && \
((crtc) = (__state)->crtcs[__i].ptr, \
(old_crtc_state) = (__state)->crtcs[__i].old_state, 1))
/** /**
* for_each_new_crtc_in_state - iterate over all CRTCs in an atomic update * for_each_new_crtc_in_state - iterate over all CRTCs in an atomic update
...@@ -685,11 +685,11 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p); ...@@ -685,11 +685,11 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
*/ */
#define for_each_new_crtc_in_state(__state, crtc, new_crtc_state, __i) \ #define for_each_new_crtc_in_state(__state, crtc, new_crtc_state, __i) \
for ((__i) = 0; \ for ((__i) = 0; \
(__i) < (__state)->dev->mode_config.num_crtc && \ (__i) < (__state)->dev->mode_config.num_crtc; \
((crtc) = (__state)->crtcs[__i].ptr, \
(new_crtc_state) = (__state)->crtcs[__i].new_state, 1); \
(__i)++) \ (__i)++) \
for_each_if (crtc) for_each_if ((__state)->crtcs[__i].ptr && \
((crtc) = (__state)->crtcs[__i].ptr, \
(new_crtc_state) = (__state)->crtcs[__i].new_state, 1))
/** /**
* for_each_oldnew_plane_in_state - iterate over all planes in an atomic update * for_each_oldnew_plane_in_state - iterate over all planes in an atomic update
...@@ -705,12 +705,12 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p); ...@@ -705,12 +705,12 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
*/ */
#define for_each_oldnew_plane_in_state(__state, plane, old_plane_state, new_plane_state, __i) \ #define for_each_oldnew_plane_in_state(__state, plane, old_plane_state, new_plane_state, __i) \
for ((__i) = 0; \ for ((__i) = 0; \
(__i) < (__state)->dev->mode_config.num_total_plane && \ (__i) < (__state)->dev->mode_config.num_total_plane; \
((plane) = (__state)->planes[__i].ptr, \
(old_plane_state) = (__state)->planes[__i].old_state, \
(new_plane_state) = (__state)->planes[__i].new_state, 1); \
(__i)++) \ (__i)++) \
for_each_if (plane) for_each_if ((__state)->planes[__i].ptr && \
((plane) = (__state)->planes[__i].ptr, \
(old_plane_state) = (__state)->planes[__i].old_state,\
(new_plane_state) = (__state)->planes[__i].new_state, 1))
/** /**
* for_each_old_plane_in_state - iterate over all planes in an atomic update * for_each_old_plane_in_state - iterate over all planes in an atomic update
...@@ -725,12 +725,11 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p); ...@@ -725,12 +725,11 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
*/ */
#define for_each_old_plane_in_state(__state, plane, old_plane_state, __i) \ #define for_each_old_plane_in_state(__state, plane, old_plane_state, __i) \
for ((__i) = 0; \ for ((__i) = 0; \
(__i) < (__state)->dev->mode_config.num_total_plane && \ (__i) < (__state)->dev->mode_config.num_total_plane; \
((plane) = (__state)->planes[__i].ptr, \
(old_plane_state) = (__state)->planes[__i].old_state, 1); \
(__i)++) \ (__i)++) \
for_each_if (plane) for_each_if ((__state)->planes[__i].ptr && \
((plane) = (__state)->planes[__i].ptr, \
(old_plane_state) = (__state)->planes[__i].old_state, 1))
/** /**
* for_each_new_plane_in_state - iterate over all planes in an atomic update * for_each_new_plane_in_state - iterate over all planes in an atomic update
* @__state: &struct drm_atomic_state pointer * @__state: &struct drm_atomic_state pointer
...@@ -744,11 +743,11 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p); ...@@ -744,11 +743,11 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
*/ */
#define for_each_new_plane_in_state(__state, plane, new_plane_state, __i) \ #define for_each_new_plane_in_state(__state, plane, new_plane_state, __i) \
for ((__i) = 0; \ for ((__i) = 0; \
(__i) < (__state)->dev->mode_config.num_total_plane && \ (__i) < (__state)->dev->mode_config.num_total_plane; \
((plane) = (__state)->planes[__i].ptr, \
(new_plane_state) = (__state)->planes[__i].new_state, 1); \
(__i)++) \ (__i)++) \
for_each_if (plane) for_each_if ((__state)->planes[__i].ptr && \
((plane) = (__state)->planes[__i].ptr, \
(new_plane_state) = (__state)->planes[__i].new_state, 1))
/** /**
* for_each_oldnew_private_obj_in_state - iterate over all private objects in an atomic update * for_each_oldnew_private_obj_in_state - iterate over all private objects in an atomic update
...@@ -768,8 +767,7 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p); ...@@ -768,8 +767,7 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
((obj) = (__state)->private_objs[__i].ptr, \ ((obj) = (__state)->private_objs[__i].ptr, \
(old_obj_state) = (__state)->private_objs[__i].old_state, \ (old_obj_state) = (__state)->private_objs[__i].old_state, \
(new_obj_state) = (__state)->private_objs[__i].new_state, 1); \ (new_obj_state) = (__state)->private_objs[__i].new_state, 1); \
(__i)++) \ (__i)++)
for_each_if (obj)
/** /**
* for_each_old_private_obj_in_state - iterate over all private objects in an atomic update * for_each_old_private_obj_in_state - iterate over all private objects in an atomic update
...@@ -787,8 +785,7 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p); ...@@ -787,8 +785,7 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
(__i) < (__state)->num_private_objs && \ (__i) < (__state)->num_private_objs && \
((obj) = (__state)->private_objs[__i].ptr, \ ((obj) = (__state)->private_objs[__i].ptr, \
(old_obj_state) = (__state)->private_objs[__i].old_state, 1); \ (old_obj_state) = (__state)->private_objs[__i].old_state, 1); \
(__i)++) \ (__i)++)
for_each_if (obj)
/** /**
* for_each_new_private_obj_in_state - iterate over all private objects in an atomic update * for_each_new_private_obj_in_state - iterate over all private objects in an atomic update
...@@ -806,8 +803,7 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p); ...@@ -806,8 +803,7 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
(__i) < (__state)->num_private_objs && \ (__i) < (__state)->num_private_objs && \
((obj) = (__state)->private_objs[__i].ptr, \ ((obj) = (__state)->private_objs[__i].ptr, \
(new_obj_state) = (__state)->private_objs[__i].new_state, 1); \ (new_obj_state) = (__state)->private_objs[__i].new_state, 1); \
(__i)++) \ (__i)++)
for_each_if (obj)
/** /**
* drm_atomic_crtc_needs_modeset - compute combined modeset need * drm_atomic_crtc_needs_modeset - compute combined modeset need
......
...@@ -29,6 +29,8 @@ int drm_of_find_panel_or_bridge(const struct device_node *np, ...@@ -29,6 +29,8 @@ int drm_of_find_panel_or_bridge(const struct device_node *np,
int port, int endpoint, int port, int endpoint,
struct drm_panel **panel, struct drm_panel **panel,
struct drm_bridge **bridge); struct drm_bridge **bridge);
int drm_of_panel_bridge_remove(const struct device_node *np,
int port, int endpoint);
#else #else
static inline uint32_t drm_of_find_possible_crtcs(struct drm_device *dev, static inline uint32_t drm_of_find_possible_crtcs(struct drm_device *dev,
struct device_node *port) struct device_node *port)
...@@ -65,6 +67,12 @@ static inline int drm_of_find_panel_or_bridge(const struct device_node *np, ...@@ -65,6 +67,12 @@ static inline int drm_of_find_panel_or_bridge(const struct device_node *np,
{ {
return -EINVAL; return -EINVAL;
} }
static inline int drm_of_panel_bridge_remove(const struct device_node *np,
int port, int endpoint)
{
return -EINVAL;
}
#endif #endif
static inline int drm_of_encoder_active_endpoint_id(struct device_node *node, static inline int drm_of_encoder_active_endpoint_id(struct device_node *node,
......
...@@ -248,9 +248,12 @@ dma_fence_get_rcu_safe(struct dma_fence * __rcu *fencep) ...@@ -248,9 +248,12 @@ dma_fence_get_rcu_safe(struct dma_fence * __rcu *fencep)
struct dma_fence *fence; struct dma_fence *fence;
fence = rcu_dereference(*fencep); fence = rcu_dereference(*fencep);
if (!fence || !dma_fence_get_rcu(fence)) if (!fence)
return NULL; return NULL;
if (!dma_fence_get_rcu(fence))
continue;
/* The atomic_inc_not_zero() inside dma_fence_get_rcu() /* The atomic_inc_not_zero() inside dma_fence_get_rcu()
* provides a full memory barrier upon success (such as now). * provides a full memory barrier upon success (such as now).
* This is paired with the write barrier from assigning * This is paired with the write barrier from assigning
......
...@@ -139,6 +139,45 @@ struct reg_sequence { ...@@ -139,6 +139,45 @@ struct reg_sequence {
pollret ?: ((cond) ? 0 : -ETIMEDOUT); \ pollret ?: ((cond) ? 0 : -ETIMEDOUT); \
}) })
/**
* regmap_field_read_poll_timeout - Poll until a condition is met or timeout
*
* @field: Regmap field to read from
* @val: Unsigned integer variable to read the value into
* @cond: Break condition (usually involving @val)
* @sleep_us: Maximum time to sleep between reads in us (0
* tight-loops). Should be less than ~20ms since usleep_range
* is used (see Documentation/timers/timers-howto.txt).
* @timeout_us: Timeout in us, 0 means never timeout
*
* Returns 0 on success and -ETIMEDOUT upon a timeout or the regmap_field_read
* error return value in case of a error read. In the two former cases,
* the last read value at @addr is stored in @val. Must not be called
* from atomic context if sleep_us or timeout_us are used.
*
* This is modelled after the readx_poll_timeout macros in linux/iopoll.h.
*/
#define regmap_field_read_poll_timeout(field, val, cond, sleep_us, timeout_us) \
({ \
ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); \
int pollret; \
might_sleep_if(sleep_us); \
for (;;) { \
pollret = regmap_field_read((field), &(val)); \
if (pollret) \
break; \
if (cond) \
break; \
if (timeout_us && ktime_compare(ktime_get(), timeout) > 0) { \
pollret = regmap_field_read((field), &(val)); \
break; \
} \
if (sleep_us) \
usleep_range((sleep_us >> 2) + 1, sleep_us); \
} \
pollret ?: ((cond) ? 0 : -ETIMEDOUT); \
})
#ifdef CONFIG_REGMAP #ifdef CONFIG_REGMAP
enum regmap_endian { enum regmap_endian {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment