forked from luck/tmp_suning_uos_patched
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() ...
This commit is contained in:
commit
d0f6d40130
|
@ -68,6 +68,8 @@ Optional properties:
|
|||
- 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,
|
||||
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:
|
||||
|
||||
|
@ -89,6 +91,8 @@ Example
|
|||
reg = <39>;
|
||||
interrupt-parent = <&gpio3>;
|
||||
interrupts = <29 IRQ_TYPE_EDGE_FALLING>;
|
||||
clocks = <&cec_clock>;
|
||||
clock-names = "cec";
|
||||
|
||||
adi,input-depth = <8>;
|
||||
adi,input-colorspace = "rgb";
|
||||
|
|
49
Documentation/devicetree/bindings/display/bridge/sii9234.txt
Normal file
49
Documentation/devicetree/bindings/display/bridge/sii9234.txt
Normal file
|
@ -0,0 +1,49 @@
|
|||
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>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,49 @@
|
|||
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.
|
|||
Required properties:
|
||||
- compatible: value must be one of:
|
||||
* allwinner,sun5i-a10s-hdmi
|
||||
* allwinner,sun6i-a31-hdmi
|
||||
- reg: base address and size of memory-mapped region
|
||||
- interrupts: interrupt associated to this IP
|
||||
- clocks: phandles to the clocks feeding the HDMI encoder
|
||||
* ahb: the HDMI interface clock
|
||||
* mod: the HDMI module clock
|
||||
* ddc: the HDMI ddc clock (A31 only)
|
||||
* pll-0: the first video PLL
|
||||
* pll-1: the second video PLL
|
||||
- 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
|
||||
* ddc-tx: The channel for DDC transmission
|
||||
* ddc-rx: The channel for DDC reception
|
||||
|
|
|
@ -266,8 +266,7 @@ EXPORT_SYMBOL(reservation_object_add_excl_fence);
|
|||
* @dst: the destination reservation object
|
||||
* @src: the source reservation object
|
||||
*
|
||||
* Copy all fences from src to dst. Both src->lock as well as dst-lock must be
|
||||
* held.
|
||||
* Copy all fences from src to dst. dst-lock must be held.
|
||||
*/
|
||||
int reservation_object_copy_fences(struct reservation_object *dst,
|
||||
struct reservation_object *src)
|
||||
|
@ -277,33 +276,62 @@ int reservation_object_copy_fences(struct reservation_object *dst,
|
|||
size_t size;
|
||||
unsigned i;
|
||||
|
||||
src_list = reservation_object_get_list(src);
|
||||
rcu_read_lock();
|
||||
src_list = rcu_dereference(src->fence);
|
||||
|
||||
retry:
|
||||
if (src_list) {
|
||||
size = offsetof(typeof(*src_list),
|
||||
shared[src_list->shared_count]);
|
||||
unsigned shared_count = src_list->shared_count;
|
||||
|
||||
size = offsetof(typeof(*src_list), shared[shared_count]);
|
||||
rcu_read_unlock();
|
||||
|
||||
dst_list = kmalloc(size, GFP_KERNEL);
|
||||
if (!dst_list)
|
||||
return -ENOMEM;
|
||||
|
||||
dst_list->shared_count = src_list->shared_count;
|
||||
dst_list->shared_max = src_list->shared_count;
|
||||
for (i = 0; i < src_list->shared_count; ++i)
|
||||
dst_list->shared[i] =
|
||||
dma_fence_get(src_list->shared[i]);
|
||||
rcu_read_lock();
|
||||
src_list = rcu_dereference(src->fence);
|
||||
if (!src_list || src_list->shared_count > shared_count) {
|
||||
kfree(dst_list);
|
||||
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 {
|
||||
dst_list = NULL;
|
||||
}
|
||||
|
||||
new = dma_fence_get_rcu_safe(&src->fence_excl);
|
||||
rcu_read_unlock();
|
||||
|
||||
kfree(dst->staged);
|
||||
dst->staged = NULL;
|
||||
|
||||
src_list = reservation_object_get_list(dst);
|
||||
|
||||
old = reservation_object_get_excl(dst);
|
||||
new = reservation_object_get_excl(src);
|
||||
|
||||
dma_fence_get(new);
|
||||
|
||||
preempt_disable();
|
||||
write_seqcount_begin(&dst->seq);
|
||||
|
|
|
@ -71,7 +71,7 @@ config DRM_PARADE_PS8622
|
|||
|
||||
config DRM_SIL_SII8620
|
||||
tristate "Silicon Image SII8620 HDMI/MHL bridge"
|
||||
depends on OF
|
||||
depends on OF && RC_CORE
|
||||
select DRM_KMS_HELPER
|
||||
help
|
||||
Silicon Image SII8620 HDMI/MHL bridge chip driver.
|
||||
|
@ -84,6 +84,14 @@ config DRM_SII902X
|
|||
---help---
|
||||
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
|
||||
tristate "Toshiba TC358767 eDP bridge"
|
||||
depends on OF
|
||||
|
|
|
@ -6,6 +6,7 @@ obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
|
|||
obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
|
||||
obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
|
||||
obj-$(CONFIG_DRM_SII902X) += sii902x.o
|
||||
obj-$(CONFIG_DRM_SII9234) += sii9234.o
|
||||
obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o
|
||||
obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
|
||||
obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/
|
||||
|
|
|
@ -21,3 +21,11 @@ config DRM_I2C_ADV7533
|
|||
default y
|
||||
help
|
||||
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.
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
adv7511-y := adv7511_drv.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
|
||||
obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o
|
||||
|
|
|
@ -195,6 +195,25 @@
|
|||
#define ADV7511_PACKET_GM(x) ADV7511_PACKET(5, 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 {
|
||||
ADV7511_INPUT_CLOCK_1X,
|
||||
ADV7511_INPUT_CLOCK_2X,
|
||||
|
@ -297,6 +316,8 @@ enum adv7511_type {
|
|||
ADV7533,
|
||||
};
|
||||
|
||||
#define ADV7511_MAX_ADDRS 3
|
||||
|
||||
struct adv7511 {
|
||||
struct i2c_client *i2c_main;
|
||||
struct i2c_client *i2c_edid;
|
||||
|
@ -341,15 +362,27 @@ struct adv7511 {
|
|||
|
||||
enum adv7511_type type;
|
||||
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
|
||||
void adv7533_dsi_power_on(struct adv7511 *adv);
|
||||
void adv7533_dsi_power_off(struct adv7511 *adv);
|
||||
void adv7533_mode_set(struct adv7511 *adv, struct drm_display_mode *mode);
|
||||
int adv7533_patch_registers(struct adv7511 *adv);
|
||||
void adv7533_uninit_cec(struct adv7511 *adv);
|
||||
int adv7533_init_cec(struct adv7511 *adv);
|
||||
int adv7533_patch_cec_registers(struct adv7511 *adv);
|
||||
int adv7533_attach_dsi(struct adv7511 *adv);
|
||||
void adv7533_detach_dsi(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)
|
|||
return -ENODEV;
|
||||
}
|
||||
|
||||
static inline void adv7533_uninit_cec(struct adv7511 *adv)
|
||||
{
|
||||
}
|
||||
|
||||
static inline int adv7533_init_cec(struct adv7511 *adv)
|
||||
static inline int adv7533_patch_cec_registers(struct adv7511 *adv)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
|
337
drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
Normal file
337
drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
Normal file
|
@ -0,0 +1,337 @@
|
|||
/*
|
||||
* 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 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_edid.h>
|
||||
|
||||
#include <media/cec.h>
|
||||
|
||||
#include "adv7511.h"
|
||||
|
||||
/* ADI recommended values for proper operation. */
|
||||
|
@ -336,8 +339,10 @@ static void __adv7511_power_on(struct adv7511 *adv7511)
|
|||
*/
|
||||
regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(0),
|
||||
ADV7511_INT0_EDID_READY | ADV7511_INT0_HPD);
|
||||
regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(1),
|
||||
ADV7511_INT1_DDC_ERROR);
|
||||
regmap_update_bits(adv7511->regmap,
|
||||
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)
|
|||
regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER,
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -423,6 +431,8 @@ static void adv7511_hpd_work(struct work_struct *work)
|
|||
|
||||
if (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);
|
||||
}
|
||||
}
|
||||
|
@ -453,6 +463,10 @@ static int adv7511_irq_process(struct adv7511 *adv7511, bool process_hpd)
|
|||
wake_up_all(&adv7511->wq);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DRM_I2C_ADV7511_CEC
|
||||
adv7511_cec_irq_process(adv7511, irq1);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -595,6 +609,8 @@ static int adv7511_get_modes(struct adv7511 *adv7511,
|
|||
|
||||
kfree(edid);
|
||||
|
||||
cec_s_phys_addr_from_edid(adv7511->cec_adap, edid);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
@ -919,6 +935,65 @@ static void adv7511_uninit_regulators(struct adv7511 *adv)
|
|||
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,
|
||||
struct adv7511_link_config *config)
|
||||
{
|
||||
|
@ -1009,6 +1084,7 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
|
|||
struct device *dev = &i2c->dev;
|
||||
unsigned int main_i2c_addr = i2c->addr << 1;
|
||||
unsigned int edid_i2c_addr = main_i2c_addr + 4;
|
||||
unsigned int offset;
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
|
@ -1092,11 +1168,9 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
|
|||
goto uninit_regulators;
|
||||
}
|
||||
|
||||
if (adv7511->type == ADV7533) {
|
||||
ret = adv7533_init_cec(adv7511);
|
||||
if (ret)
|
||||
goto err_i2c_unregister_edid;
|
||||
}
|
||||
ret = adv7511_init_cec_regmap(adv7511);
|
||||
if (ret)
|
||||
goto err_i2c_unregister_edid;
|
||||
|
||||
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)
|
|||
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);
|
||||
|
||||
i2c_set_clientdata(i2c, adv7511);
|
||||
|
@ -1129,10 +1199,23 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
|
|||
|
||||
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;
|
||||
|
||||
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:
|
||||
i2c_unregister_device(adv7511->i2c_edid);
|
||||
uninit_regulators:
|
||||
|
@ -1145,10 +1228,11 @@ static int adv7511_remove(struct i2c_client *i2c)
|
|||
{
|
||||
struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
|
||||
|
||||
if (adv7511->type == ADV7533) {
|
||||
if (adv7511->type == ADV7533)
|
||||
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);
|
||||
|
||||
|
@ -1156,6 +1240,8 @@ static int adv7511_remove(struct i2c_client *i2c)
|
|||
|
||||
adv7511_audio_exit(adv7511);
|
||||
|
||||
cec_unregister_adapter(adv7511->cec_adap);
|
||||
|
||||
i2c_unregister_device(adv7511->i2c_edid);
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -32,14 +32,6 @@ static const struct reg_sequence adv7533_cec_fixed_registers[] = {
|
|||
{ 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)
|
||||
{
|
||||
struct mipi_dsi_device *dsi = adv->dsi;
|
||||
|
@ -145,37 +137,11 @@ int adv7533_patch_registers(struct adv7511 *adv)
|
|||
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;
|
||||
|
||||
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,
|
||||
return regmap_register_patch(adv->regmap_cec,
|
||||
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)
|
||||
|
|
|
@ -188,7 +188,15 @@ EXPORT_SYMBOL(drm_panel_bridge_add);
|
|||
*/
|
||||
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);
|
||||
devm_kfree(panel_bridge->panel->dev, bridge);
|
||||
|
|
994
drivers/gpu/drm/bridge/sii9234.c
Normal file
994
drivers/gpu/drm/bridge/sii9234.c
Normal file
|
@ -0,0 +1,994 @@
|
|||
/*
|
||||
* 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 @@
|
|||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <media/rc-core.h>
|
||||
|
||||
#include "sil-sii8620.h"
|
||||
|
||||
#define SII8620_BURST_BUF_LEN 288
|
||||
|
@ -58,6 +60,7 @@ enum sii8620_mt_state {
|
|||
struct sii8620 {
|
||||
struct drm_bridge bridge;
|
||||
struct device *dev;
|
||||
struct rc_dev *rc_dev;
|
||||
struct clk *clk_xtal;
|
||||
struct gpio_desc *gpio_reset;
|
||||
struct gpio_desc *gpio_int;
|
||||
|
@ -431,6 +434,16 @@ static void sii8620_mt_rap(struct sii8620 *ctx, u8 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,
|
||||
struct sii8620_mt_msg *msg)
|
||||
{
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
u8 ints[MHL_INT_SIZE];
|
||||
|
@ -1804,19 +1836,25 @@ static void sii8620_msc_mt_done(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];
|
||||
|
||||
if (!msg)
|
||||
return;
|
||||
|
||||
sii8620_read_buf(ctx, REG_MSC_MR_MSC_MSG_RCVD_1ST_DATA, buf, 2);
|
||||
|
||||
switch (buf[0]) {
|
||||
case MHL_MSC_MSG_RAPK:
|
||||
msg = sii8620_msc_msg_first(ctx);
|
||||
if (!msg)
|
||||
return;
|
||||
msg->ret = buf[1];
|
||||
ctx->mt_state = MT_STATE_DONE;
|
||||
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:
|
||||
dev_err(ctx->dev, "%s message type %d,%d not supported",
|
||||
__func__, buf[0], buf[1]);
|
||||
|
@ -2102,11 +2140,57 @@ static void sii8620_cable_in(struct sii8620 *ctx)
|
|||
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)
|
||||
{
|
||||
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,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
|
@ -2151,6 +2235,8 @@ static bool sii8620_mode_fixup(struct drm_bridge *bridge,
|
|||
}
|
||||
|
||||
static const struct drm_bridge_funcs sii8620_bridge_funcs = {
|
||||
.attach = sii8620_attach,
|
||||
.detach = sii8620_detach,
|
||||
.mode_fixup = sii8620_mode_fixup,
|
||||
};
|
||||
|
||||
|
@ -2217,8 +2303,8 @@ static int sii8620_remove(struct i2c_client *client)
|
|||
struct sii8620 *ctx = i2c_get_clientdata(client);
|
||||
|
||||
disable_irq(to_i2c_client(ctx->dev)->irq);
|
||||
drm_bridge_remove(&ctx->bridge);
|
||||
sii8620_hw_off(ctx);
|
||||
drm_bridge_remove(&ctx->bridge);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -221,7 +221,6 @@ struct dw_mipi_dsi {
|
|||
struct drm_bridge bridge;
|
||||
struct mipi_dsi_host dsi_host;
|
||||
struct drm_bridge *panel_bridge;
|
||||
bool is_panel_bridge;
|
||||
struct device *dev;
|
||||
void __iomem *base;
|
||||
|
||||
|
@ -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);
|
||||
if (IS_ERR(bridge))
|
||||
return PTR_ERR(bridge);
|
||||
dsi->is_panel_bridge = true;
|
||||
}
|
||||
|
||||
dsi->panel_bridge = bridge;
|
||||
|
@ -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);
|
||||
|
||||
if (dsi->is_panel_bridge)
|
||||
drm_panel_bridge_remove(dsi->panel_bridge);
|
||||
drm_of_panel_bridge_remove(host->dev->of_node, 1, 0);
|
||||
|
||||
drm_bridge_remove(&dsi->bridge);
|
||||
|
||||
|
|
|
@ -182,9 +182,6 @@ void drm_atomic_state_default_clear(struct drm_atomic_state *state)
|
|||
for (i = 0; i < state->num_private_objs; i++) {
|
||||
struct drm_private_obj *obj = state->private_objs[i].ptr;
|
||||
|
||||
if (!obj)
|
||||
continue;
|
||||
|
||||
obj->funcs->atomic_destroy_state(obj,
|
||||
state->private_objs[i].state);
|
||||
state->private_objs[i].ptr = NULL;
|
||||
|
|
|
@ -3052,6 +3052,7 @@ int drm_atomic_helper_resume(struct drm_device *dev,
|
|||
drm_modeset_backoff(&ctx);
|
||||
}
|
||||
|
||||
drm_atomic_state_put(state);
|
||||
drm_modeset_drop_locks(&ctx);
|
||||
drm_modeset_acquire_fini(&ctx);
|
||||
|
||||
|
|
|
@ -137,8 +137,10 @@ EXPORT_SYMBOL(drm_dp_link_train_channel_eq_delay);
|
|||
u8 drm_dp_link_rate_to_bw_code(int link_rate)
|
||||
{
|
||||
switch (link_rate) {
|
||||
case 162000:
|
||||
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;
|
||||
case 270000:
|
||||
return DP_LINK_BW_2_7;
|
||||
|
@ -151,8 +153,9 @@ EXPORT_SYMBOL(drm_dp_link_rate_to_bw_code);
|
|||
int drm_dp_bw_code_to_link_rate(u8 link_bw)
|
||||
{
|
||||
switch (link_bw) {
|
||||
case DP_LINK_BW_1_62:
|
||||
default:
|
||||
WARN(1, "unknown DP link BW code %x, using 162000\n", link_bw);
|
||||
case DP_LINK_BW_1_62:
|
||||
return 162000;
|
||||
case DP_LINK_BW_2_7:
|
||||
return 270000;
|
||||
|
|
|
@ -27,19 +27,24 @@
|
|||
* DOC: overview
|
||||
*
|
||||
* 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
|
||||
* drm_gem_fb_create() and everything is wired up automatically. But all
|
||||
* parts can be used individually.
|
||||
* drm_gem_fb_create() and everything is wired up automatically. Other drivers
|
||||
* can use all parts independently.
|
||||
*/
|
||||
|
||||
/**
|
||||
* drm_gem_fb_get_obj() - Get GEM object for framebuffer
|
||||
* @fb: The framebuffer
|
||||
* @plane: Which plane
|
||||
* drm_gem_fb_get_obj() - Get GEM object backing the framebuffer
|
||||
* @fb: Framebuffer
|
||||
* @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,
|
||||
unsigned int plane)
|
||||
|
@ -82,7 +87,7 @@ drm_gem_fb_alloc(struct drm_device *dev,
|
|||
|
||||
/**
|
||||
* 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
|
||||
* itself. Drivers can use this as their &drm_framebuffer_funcs->destroy
|
||||
|
@ -102,12 +107,13 @@ EXPORT_SYMBOL(drm_gem_fb_destroy);
|
|||
|
||||
/**
|
||||
* drm_gem_fb_create_handle - Create handle for GEM backed framebuffer
|
||||
* @fb: DRM framebuffer
|
||||
* @file: drm file
|
||||
* @handle: handle created
|
||||
* @fb: Framebuffer
|
||||
* @file: DRM file to register the handle for
|
||||
* @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
|
||||
* callback.
|
||||
* callback. The GETFB IOCTL calls into this callback.
|
||||
*
|
||||
* Returns:
|
||||
* 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,
|
|||
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
|
||||
* callback
|
||||
* @dev: DRM device
|
||||
* @file: drm file for the ioctl call
|
||||
* @mode_cmd: metadata from the userspace fb creation request
|
||||
* @file: DRM file that holds the GEM handle(s) backing the framebuffer
|
||||
* @mode_cmd: Metadata from the userspace framebuffer creation request
|
||||
* @funcs: vtable to be used for the new framebuffer object
|
||||
*
|
||||
* 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
|
||||
* need to change &drm_framebuffer_funcs.
|
||||
* The function does buffer size validation.
|
||||
*
|
||||
* Returns:
|
||||
* Pointer to a &drm_framebuffer on success or an error pointer on failure.
|
||||
*/
|
||||
struct drm_framebuffer *
|
||||
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 = {
|
|||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @file: drm file for the ioctl call
|
||||
* @mode_cmd: metadata from the userspace fb creation request
|
||||
* @file: DRM file that holds the GEM handle(s) backing the framebuffer
|
||||
* @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
|
||||
* checked before calling this function. The function does buffer size
|
||||
* validation. Use drm_gem_fb_create_with_funcs() if you need to set
|
||||
* &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 *
|
||||
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);
|
||||
|
||||
/**
|
||||
* drm_gem_fb_prepare_fb() - Prepare gem framebuffer
|
||||
* @plane: Which plane
|
||||
* @state: Plane state attach fence to
|
||||
* drm_gem_fb_prepare_fb() - Prepare a GEM backed framebuffer
|
||||
* @plane: Plane
|
||||
* @state: Plane state the fence will be attached to
|
||||
*
|
||||
* This can be used as the &drm_plane_helper_funcs.prepare_fb hook.
|
||||
*
|
||||
* This function checks if the plane FB has an dma-buf attached, extracts
|
||||
* the exclusive fence and attaches it to plane state for the atomic helper
|
||||
* to wait on.
|
||||
* 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
|
||||
* exclusive fence and attaches it to the plane state for the atomic helper to
|
||||
* wait on. This function can be used as the &drm_plane_helper_funcs.prepare_fb
|
||||
* callback.
|
||||
*
|
||||
* 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
|
||||
|
@ -246,17 +266,19 @@ int drm_gem_fb_prepare_fb(struct drm_plane *plane,
|
|||
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
|
||||
* @sizes: fbdev size description
|
||||
* @pitch_align: optional pitch alignment
|
||||
* @pitch_align: Optional pitch alignment
|
||||
* @obj: GEM object backing the framebuffer
|
||||
* @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:
|
||||
* 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 *
|
||||
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,
|
|||
return ret;
|
||||
}
|
||||
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)
|
|||
struct drm_crtc *crtc;
|
||||
struct drm_encoder *encoder;
|
||||
|
||||
if (pipe < 0 || pipe >= priv->num_crtcs)
|
||||
if (pipe >= priv->num_crtcs)
|
||||
return 0;
|
||||
|
||||
crtc = priv->crtcs[pipe];
|
||||
|
|
|
@ -82,6 +82,14 @@ config DRM_PANEL_PANASONIC_VVX10F034N00
|
|||
WUXGA (1920x1200) Novatek NT1397-based DSI panel as found in some
|
||||
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
|
||||
tristate "Samsung S6E3HA2 DSI video mode panel"
|
||||
depends on OF
|
||||
|
|
|
@ -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_ORISETECH_OTM8009A) += panel-orisetech-otm8009a.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_S6E3HA2) += panel-samsung-s6e3ha2.o
|
||||
obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63J0X03) += panel-samsung-s6e63j0x03.o
|
||||
|
|
514
drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c
Normal file
514
drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c
Normal file
|
@ -0,0 +1,514 @@
|
|||
/*
|
||||
* 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 = {
|
|||
.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;
|
||||
int ret;
|
||||
|
||||
|
@ -807,7 +806,7 @@ static int ltdc_encoder_init(struct drm_device *ddev)
|
|||
drm_encoder_init(ddev, encoder, <dc_encoder_funcs,
|
||||
DRM_MODE_ENCODER_DPI, NULL);
|
||||
|
||||
ret = drm_bridge_attach(encoder, ldev->bridge, NULL);
|
||||
ret = drm_bridge_attach(encoder, bridge, NULL);
|
||||
if (ret) {
|
||||
drm_encoder_cleanup(encoder);
|
||||
return -EINVAL;
|
||||
|
@ -936,12 +935,9 @@ int ltdc_load(struct drm_device *ddev)
|
|||
ret = PTR_ERR(bridge);
|
||||
goto err;
|
||||
}
|
||||
ldev->is_panel_bridge = true;
|
||||
}
|
||||
|
||||
ldev->bridge = bridge;
|
||||
|
||||
ret = ltdc_encoder_init(ddev);
|
||||
ret = ltdc_encoder_init(ddev, bridge);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to init encoder\n");
|
||||
goto err;
|
||||
|
@ -972,8 +968,7 @@ int ltdc_load(struct drm_device *ddev)
|
|||
return 0;
|
||||
|
||||
err:
|
||||
if (ldev->is_panel_bridge)
|
||||
drm_panel_bridge_remove(bridge);
|
||||
drm_panel_bridge_remove(bridge);
|
||||
|
||||
clk_disable_unprepare(ldev->pixel_clk);
|
||||
|
||||
|
@ -986,8 +981,7 @@ void ltdc_unload(struct drm_device *ddev)
|
|||
|
||||
DRM_DEBUG_DRIVER("\n");
|
||||
|
||||
if (ldev->is_panel_bridge)
|
||||
drm_panel_bridge_remove(ldev->bridge);
|
||||
drm_of_panel_bridge_remove(ddev->dev->of_node, 0, 0);
|
||||
|
||||
clk_disable_unprepare(ldev->pixel_clk);
|
||||
}
|
||||
|
|
|
@ -24,8 +24,6 @@ struct ltdc_device {
|
|||
struct drm_fbdev_cma *fbdev;
|
||||
void __iomem *regs;
|
||||
struct clk *pixel_clk; /* lcd pixel clock */
|
||||
struct drm_bridge *bridge;
|
||||
bool is_panel_bridge;
|
||||
struct mutex err_lock; /* protecting error_status */
|
||||
struct ltdc_caps caps;
|
||||
u32 error_status;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <drm/drm_connector.h>
|
||||
#include <drm/drm_encoder.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include <media/cec-pin.h>
|
||||
|
||||
|
@ -58,10 +59,13 @@
|
|||
#define SUN4I_HDMI_PAD_CTRL0_TXEN BIT(23)
|
||||
|
||||
#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_AMPCK_OPT BIT(22)
|
||||
#define SUN4I_HDMI_PAD_CTRL1_EMP_OPT BIT(20)
|
||||
#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_DENCK BIT(14)
|
||||
#define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n) (((n) & 7) << 10)
|
||||
|
@ -152,21 +156,106 @@
|
|||
|
||||
#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 {
|
||||
SUN4I_HDMI_PKT_AVI = 2,
|
||||
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 drm_connector connector;
|
||||
struct drm_encoder encoder;
|
||||
struct device *dev;
|
||||
|
||||
void __iomem *base;
|
||||
struct regmap *regmap;
|
||||
|
||||
/* Reset control */
|
||||
struct reset_control *reset;
|
||||
|
||||
/* Parent clocks */
|
||||
struct clk *bus_clk;
|
||||
struct clk *mod_clk;
|
||||
struct clk *ddc_parent_clk;
|
||||
struct clk *pll0_clk;
|
||||
struct clk *pll1_clk;
|
||||
|
||||
|
@ -176,10 +265,28 @@ struct sun4i_hdmi {
|
|||
|
||||
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;
|
||||
|
||||
bool hdmi_monitor;
|
||||
struct cec_adapter *cec_adap;
|
||||
|
||||
const struct sun4i_hdmi_variant *variant;
|
||||
};
|
||||
|
||||
int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
*/
|
||||
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include "sun4i_tcon.h"
|
||||
#include "sun4i_hdmi.h"
|
||||
|
@ -18,6 +19,9 @@
|
|||
struct sun4i_ddc {
|
||||
struct clk_hw hw;
|
||||
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)
|
||||
|
@ -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,
|
||||
unsigned long parent_rate,
|
||||
const u8 pre_div,
|
||||
const u8 m_offset,
|
||||
u8 *m, u8 *n)
|
||||
{
|
||||
unsigned long best_rate = 0;
|
||||
|
@ -36,7 +42,8 @@ static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
|
|||
for (_n = 0; _n < 8; _n++) {
|
||||
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)
|
||||
continue;
|
||||
|
@ -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,
|
||||
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,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct sun4i_ddc *ddc = hw_to_ddc(hw);
|
||||
u32 reg;
|
||||
unsigned int reg;
|
||||
u8 m, n;
|
||||
|
||||
reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
|
||||
m = (reg >> 3) & 0x7;
|
||||
regmap_field_read(ddc->reg, ®);
|
||||
m = (reg >> 3) & 0xf;
|
||||
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,
|
||||
|
@ -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);
|
||||
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),
|
||||
ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
|
||||
regmap_field_write(ddc->reg,
|
||||
SUN4I_HDMI_DDC_CLK_M(div_m) |
|
||||
SUN4I_HDMI_DDC_CLK_N(div_n));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -111,6 +124,11 @@ int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
|
|||
if (!ddc)
|
||||
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.ops = &sun4i_ddc_ops;
|
||||
init.parent_names = &parent_name;
|
||||
|
@ -118,6 +136,8 @@ int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
|
|||
|
||||
ddc->hdmi = hdmi;
|
||||
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);
|
||||
if (IS_ERR(hdmi->ddc_clk))
|
||||
|
|
|
@ -20,8 +20,11 @@
|
|||
#include <linux/clk.h>
|
||||
#include <linux/component.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include "sun4i_backend.h"
|
||||
#include "sun4i_crtc.h"
|
||||
|
@ -267,6 +270,124 @@ static const struct cec_pin_ops sun4i_hdmi_cec_pin_ops = {
|
|||
};
|
||||
#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,
|
||||
void *data)
|
||||
{
|
||||
|
@ -285,6 +406,10 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
|
|||
hdmi->dev = dev;
|
||||
hdmi->drv = drv;
|
||||
|
||||
hdmi->variant = of_device_get_match_data(dev);
|
||||
if (!hdmi->variant)
|
||||
return -EINVAL;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
hdmi->base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(hdmi->base)) {
|
||||
|
@ -292,44 +417,76 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
|
|||
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");
|
||||
if (IS_ERR(hdmi->bus_clk)) {
|
||||
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);
|
||||
|
||||
hdmi->mod_clk = devm_clk_get(dev, "mod");
|
||||
if (IS_ERR(hdmi->mod_clk)) {
|
||||
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);
|
||||
|
||||
hdmi->pll0_clk = devm_clk_get(dev, "pll-0");
|
||||
if (IS_ERR(hdmi->pll0_clk)) {
|
||||
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");
|
||||
if (IS_ERR(hdmi->pll1_clk)) {
|
||||
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);
|
||||
if (ret) {
|
||||
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_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,
|
||||
writel(hdmi->variant->pad_ctrl0_init_val,
|
||||
hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG);
|
||||
|
||||
/*
|
||||
|
@ -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 &= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
|
||||
reg |= 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;
|
||||
reg |= hdmi->variant->pad_ctrl1_init_val;
|
||||
writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
|
||||
|
||||
reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
|
||||
reg &= SUN4I_HDMI_PLL_CTRL_DIV_MASK;
|
||||
reg |= 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;
|
||||
reg |= hdmi->variant->pll_ctrl_init_val;
|
||||
writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
|
||||
|
||||
ret = sun4i_hdmi_i2c_create(dev, hdmi);
|
||||
if (ret) {
|
||||
dev_err(dev, "Couldn't create the HDMI I2C adapter\n");
|
||||
return ret;
|
||||
goto err_disable_mod_clk;
|
||||
}
|
||||
|
||||
drm_encoder_helper_add(&hdmi->encoder,
|
||||
|
@ -422,6 +567,12 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
|
|||
drm_encoder_cleanup(&hdmi->encoder);
|
||||
err_del_i2c_adapter:
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -434,6 +585,8 @@ static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
|
|||
drm_connector_cleanup(&hdmi->connector);
|
||||
drm_encoder_cleanup(&hdmi->encoder);
|
||||
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 = {
|
||||
|
@ -454,7 +607,8 @@ static int sun4i_hdmi_remove(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -25,8 +25,6 @@
|
|||
|
||||
/* 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
|
||||
/* 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)
|
||||
{
|
||||
|
@ -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_TRANSFER_COMPLETE;
|
||||
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) :
|
||||
(SUN4I_HDMI_DDC_FIFO_SIZE - TX_THRESHOLD + 1));
|
||||
/*
|
||||
* Limit transfer length by FIFO threshold or FIFO size.
|
||||
* 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 */
|
||||
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG, reg,
|
||||
reg & mask, len * byte_time_ns, 100000))
|
||||
if (regmap_field_read_poll_timeout(hdmi->field_ddc_int_status, reg,
|
||||
reg & mask, len * byte_time_ns,
|
||||
100000))
|
||||
return -ETIMEDOUT;
|
||||
|
||||
if (reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK)
|
||||
return -EIO;
|
||||
|
||||
if (read)
|
||||
readsb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG, buf, len);
|
||||
readsb(hdmi->base + hdmi->variant->ddc_fifo_reg, buf, len);
|
||||
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 */
|
||||
writel(SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST,
|
||||
hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG);
|
||||
/* Clear FIFO request bit by forcing a write to that bit */
|
||||
regmap_field_force_write(hdmi->field_ddc_int_status,
|
||||
SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
@ -70,50 +77,52 @@ static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg)
|
|||
u32 reg;
|
||||
|
||||
/* Set FIFO direction */
|
||||
reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
|
||||
reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
|
||||
reg |= (msg->flags & I2C_M_RD) ?
|
||||
SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ :
|
||||
SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE;
|
||||
writel(reg, hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
|
||||
if (hdmi->variant->ddc_fifo_has_dir) {
|
||||
reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
|
||||
reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
|
||||
reg |= (msg->flags & I2C_M_RD) ?
|
||||
SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ :
|
||||
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 */
|
||||
writel(SUN4I_HDMI_DDC_ADDR_SLAVE(msg->addr),
|
||||
hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
|
||||
regmap_field_write(hdmi->field_ddc_slave_addr, msg->addr);
|
||||
|
||||
/* 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;
|
||||
reg &= ~SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK;
|
||||
reg |= SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES(RX_THRESHOLD);
|
||||
reg &= ~SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK;
|
||||
reg |= SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES(TX_THRESHOLD);
|
||||
writel(reg, hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
|
||||
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG,
|
||||
reg,
|
||||
!(reg & SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR),
|
||||
100, 2000))
|
||||
/*
|
||||
* Set FIFO RX/TX thresholds and clear FIFO
|
||||
*
|
||||
* If threshold is inclusive, we can set the TX threshold to
|
||||
* 0 instead of 1.
|
||||
*/
|
||||
regmap_field_write(hdmi->field_ddc_fifo_tx_thres,
|
||||
hdmi->variant->ddc_fifo_thres_incl ? 0 : 1);
|
||||
regmap_field_write(hdmi->field_ddc_fifo_rx_thres, RX_THRESHOLD);
|
||||
regmap_field_write(hdmi->field_ddc_fifo_clear, 1);
|
||||
if (regmap_field_read_poll_timeout(hdmi->field_ddc_fifo_clear,
|
||||
reg, !reg, 100, 2000))
|
||||
return -EIO;
|
||||
|
||||
/* 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 */
|
||||
writel(msg->flags & I2C_M_RD ?
|
||||
SUN4I_HDMI_DDC_CMD_IMPLICIT_READ :
|
||||
SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE,
|
||||
hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
|
||||
regmap_field_write(hdmi->field_ddc_cmd,
|
||||
msg->flags & I2C_M_RD ?
|
||||
SUN4I_HDMI_DDC_CMD_IMPLICIT_READ :
|
||||
SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE);
|
||||
|
||||
/* Clear interrupt status bits */
|
||||
writel(SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK |
|
||||
SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST |
|
||||
SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE,
|
||||
hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG);
|
||||
/* Clear interrupt status bits by forcing a write */
|
||||
regmap_field_force_write(hdmi->field_ddc_int_status,
|
||||
SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK |
|
||||
SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST |
|
||||
SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE);
|
||||
|
||||
/* Start command */
|
||||
reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
|
||||
writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
|
||||
hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
|
||||
regmap_field_write(hdmi->field_ddc_start, 1);
|
||||
|
||||
/* Transfer bytes */
|
||||
for (i = 0; i < msg->len; i += len) {
|
||||
|
@ -124,14 +133,12 @@ static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg)
|
|||
}
|
||||
|
||||
/* Wait for command to finish */
|
||||
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG,
|
||||
reg,
|
||||
!(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
|
||||
100, 100000))
|
||||
if (regmap_field_read_poll_timeout(hdmi->field_ddc_start,
|
||||
reg, !reg, 100, 100000))
|
||||
return -EIO;
|
||||
|
||||
/* Check for errors */
|
||||
reg = readl(hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG);
|
||||
regmap_field_read(hdmi->field_ddc_int_status, ®);
|
||||
if ((reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) ||
|
||||
!(reg & SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE)) {
|
||||
return -EIO;
|
||||
|
@ -154,21 +161,22 @@ static int sun4i_hdmi_i2c_xfer(struct i2c_adapter *adap,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Reset I2C controller */
|
||||
writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
|
||||
hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
|
||||
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
|
||||
!(reg & SUN4I_HDMI_DDC_CTRL_RESET),
|
||||
100, 2000))
|
||||
return -EIO;
|
||||
|
||||
writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
|
||||
SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
|
||||
hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
|
||||
|
||||
/* 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 */
|
||||
regmap_field_write(hdmi->field_ddc_en, 1);
|
||||
regmap_field_write(hdmi->field_ddc_reset, 1);
|
||||
if (regmap_field_read_poll_timeout(hdmi->field_ddc_reset,
|
||||
reg, !reg, 100, 2000)) {
|
||||
clk_disable_unprepare(hdmi->ddc_clk);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
regmap_field_write(hdmi->field_ddc_sck_en, 1);
|
||||
regmap_field_write(hdmi->field_ddc_sda_en, 1);
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
err = xfer_msg(hdmi, &msgs[i]);
|
||||
if (err) {
|
||||
|
@ -191,12 +199,105 @@ static const struct i2c_algorithm sun4i_hdmi_i2c_algorithm = {
|
|||
.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)
|
||||
{
|
||||
struct i2c_adapter *adap;
|
||||
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)
|
||||
return ret;
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
struct sun4i_tmds {
|
||||
struct clk_hw hw;
|
||||
struct sun4i_hdmi *hdmi;
|
||||
|
||||
u8 div_offset;
|
||||
};
|
||||
|
||||
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,
|
||||
unsigned long parent_rate,
|
||||
u8 div_offset,
|
||||
u8 *div,
|
||||
bool *half)
|
||||
{
|
||||
|
@ -35,7 +38,7 @@ static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
|
|||
u8 best_m = 0, m;
|
||||
bool is_double;
|
||||
|
||||
for (m = 1; m < 16; m++) {
|
||||
for (m = div_offset ?: 1; m < (16 + div_offset); m++) {
|
||||
u8 d;
|
||||
|
||||
for (d = 1; d < 3; d++) {
|
||||
|
@ -67,11 +70,12 @@ static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
|
|||
static int sun4i_tmds_determine_rate(struct clk_hw *hw,
|
||||
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 rate = req->rate;
|
||||
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
|
||||
|
@ -79,32 +83,38 @@ static int sun4i_tmds_determine_rate(struct clk_hw *hw,
|
|||
* clock, so we should not need to do anything.
|
||||
*/
|
||||
|
||||
parent = clk_hw_get_parent_by_index(hw, 0);
|
||||
if (!parent)
|
||||
return -EINVAL;
|
||||
for (p = 0; p < clk_hw_get_num_parents(hw); p++) {
|
||||
parent = clk_hw_get_parent_by_index(hw, p);
|
||||
if (!parent)
|
||||
continue;
|
||||
|
||||
for (i = 1; i < 3; i++) {
|
||||
for (j = 1; j < 16; j++) {
|
||||
unsigned long ideal = rate * i * j;
|
||||
unsigned long rounded;
|
||||
for (i = 1; i < 3; i++) {
|
||||
for (j = tmds->div_offset ?: 1;
|
||||
j < (16 + tmds->div_offset); j++) {
|
||||
unsigned long ideal = rate * i * j;
|
||||
unsigned long rounded;
|
||||
|
||||
rounded = clk_hw_round_rate(parent, ideal);
|
||||
rounded = clk_hw_round_rate(parent, ideal);
|
||||
|
||||
if (rounded == ideal) {
|
||||
best_parent = rounded;
|
||||
best_half = i;
|
||||
best_div = j;
|
||||
goto out;
|
||||
}
|
||||
if (rounded == ideal) {
|
||||
best_parent = rounded;
|
||||
best_half = i;
|
||||
best_div = j;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (abs(rate - rounded / i) <
|
||||
abs(rate - best_parent / best_div)) {
|
||||
best_parent = rounded;
|
||||
best_div = i;
|
||||
if (abs(rate - rounded / i) <
|
||||
abs(rate - best_parent / best_div)) {
|
||||
best_parent = rounded;
|
||||
best_div = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!parent)
|
||||
return -EINVAL;
|
||||
|
||||
out:
|
||||
req->rate = best_parent / best_half / best_div;
|
||||
req->best_parent_rate = best_parent;
|
||||
|
@ -124,7 +134,7 @@ static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
|
|||
parent_rate /= 2;
|
||||
|
||||
reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
|
||||
reg = (reg >> 4) & 0xf;
|
||||
reg = ((reg >> 4) & 0xf) + tmds->div_offset;
|
||||
if (!reg)
|
||||
reg = 1;
|
||||
|
||||
|
@ -139,7 +149,8 @@ static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
|
|||
u32 reg;
|
||||
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 &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
|
||||
|
@ -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 &= ~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);
|
||||
|
||||
return 0;
|
||||
|
@ -216,6 +227,7 @@ int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
|
|||
|
||||
tmds->hdmi = hdmi;
|
||||
tmds->hw.init = &init;
|
||||
tmds->div_offset = hdmi->variant->tmds_clk_div_offset;
|
||||
|
||||
hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
|
||||
if (IS_ERR(hdmi->tmds_clk))
|
||||
|
|
|
@ -14,9 +14,12 @@
|
|||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_encoder.h>
|
||||
#include <drm/drm_modes.h>
|
||||
#include <drm/drm_of.h>
|
||||
|
||||
#include <uapi/drm/drm_mode.h>
|
||||
|
||||
#include <linux/component.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/of_address.h>
|
||||
|
@ -109,26 +112,37 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
|
|||
}
|
||||
EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
struct sun4i_drv *drv = drm->dev_private;
|
||||
struct sun4i_tcon *tcon;
|
||||
|
||||
list_for_each_entry(tcon, &drv->tcon_list, list)
|
||||
if (tcon->id == 0)
|
||||
return tcon;
|
||||
|
||||
dev_warn(drm->dev,
|
||||
"TCON0 not found, display output muxing may not work\n");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
|
||||
struct drm_encoder *encoder)
|
||||
{
|
||||
u32 val;
|
||||
int ret = -ENOTSUPP;
|
||||
|
||||
if (!tcon->quirks->has_unknown_mux)
|
||||
return;
|
||||
if (tcon->quirks->set_mux)
|
||||
ret = tcon->quirks->set_mux(tcon, encoder);
|
||||
|
||||
if (channel != 1)
|
||||
return;
|
||||
|
||||
if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC)
|
||||
val = 1;
|
||||
else
|
||||
val = 0;
|
||||
|
||||
/*
|
||||
* FIXME: Undocumented bits
|
||||
*/
|
||||
regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val);
|
||||
DRM_DEBUG_DRIVER("Muxing encoder %s to CRTC %s: %d\n",
|
||||
encoder->name, encoder->crtc->name, ret);
|
||||
}
|
||||
EXPORT_SYMBOL(sun4i_tcon_set_mux);
|
||||
|
||||
|
@ -767,14 +781,57 @@ static int sun4i_tcon_remove(struct platform_device *pdev)
|
|||
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 = {
|
||||
.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 = {
|
||||
.has_channel_1 = true,
|
||||
.needs_de_be_mux = true,
|
||||
.set_mux = sun6i_tcon_set_mux,
|
||||
};
|
||||
|
||||
static const struct sun4i_tcon_quirks sun6i_a31s_quirks = {
|
||||
|
|
|
@ -145,10 +145,14 @@
|
|||
|
||||
#define SUN4I_TCON_MAX_CHANNELS 2
|
||||
|
||||
struct sun4i_tcon;
|
||||
|
||||
struct sun4i_tcon_quirks {
|
||||
bool has_unknown_mux; /* sun5i has undocumented mux */
|
||||
bool has_channel_1; /* a33 does not have channel 1 */
|
||||
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 {
|
||||
|
|
|
@ -97,8 +97,6 @@ struct vc4_dpi {
|
|||
|
||||
struct drm_encoder *encoder;
|
||||
struct drm_connector *connector;
|
||||
struct drm_bridge *bridge;
|
||||
bool is_panel_bridge;
|
||||
|
||||
void __iomem *regs;
|
||||
|
||||
|
@ -251,10 +249,11 @@ static int vc4_dpi_init_bridge(struct vc4_dpi *dpi)
|
|||
{
|
||||
struct device *dev = &dpi->pdev->dev;
|
||||
struct drm_panel *panel;
|
||||
struct drm_bridge *bridge;
|
||||
int ret;
|
||||
|
||||
ret = drm_of_find_panel_or_bridge(dev->of_node, 0, 0,
|
||||
&panel, &dpi->bridge);
|
||||
&panel, &bridge);
|
||||
if (ret) {
|
||||
/* If nothing was connected in the DT, that's not an
|
||||
* error.
|
||||
|
@ -265,13 +264,10 @@ static int vc4_dpi_init_bridge(struct vc4_dpi *dpi)
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (panel) {
|
||||
dpi->bridge = drm_panel_bridge_add(panel,
|
||||
DRM_MODE_CONNECTOR_DPI);
|
||||
dpi->is_panel_bridge = true;
|
||||
}
|
||||
if (panel)
|
||||
bridge = drm_panel_bridge_add(panel, DRM_MODE_CONNECTOR_DPI);
|
||||
|
||||
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)
|
||||
|
@ -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_dpi *dpi = dev_get_drvdata(dev);
|
||||
|
||||
if (dpi->is_panel_bridge)
|
||||
drm_panel_bridge_remove(dpi->bridge);
|
||||
drm_of_panel_bridge_remove(dev->of_node, 0, 0);
|
||||
|
||||
drm_encoder_cleanup(dpi->encoder);
|
||||
|
||||
|
|
|
@ -262,6 +262,10 @@ enum {
|
|||
#define MHL_RAPK_UNSUPPORTED 0x02 /* Rcvd RAP action code not supported */
|
||||
#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
|
||||
*/
|
||||
|
|
|
@ -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) \
|
||||
for ((__i) = 0; \
|
||||
(__i) < (__state)->num_connector && \
|
||||
((connector) = (__state)->connectors[__i].ptr, \
|
||||
(old_connector_state) = (__state)->connectors[__i].old_state, \
|
||||
(new_connector_state) = (__state)->connectors[__i].new_state, 1); \
|
||||
(__i)++) \
|
||||
for_each_if (connector)
|
||||
(__i) < (__state)->num_connector; \
|
||||
(__i)++) \
|
||||
for_each_if ((__state)->connectors[__i].ptr && \
|
||||
((connector) = (__state)->connectors[__i].ptr, \
|
||||
(old_connector_state) = (__state)->connectors[__i].old_state, \
|
||||
(new_connector_state) = (__state)->connectors[__i].new_state, 1))
|
||||
|
||||
/**
|
||||
* 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);
|
|||
*/
|
||||
#define for_each_old_connector_in_state(__state, connector, old_connector_state, __i) \
|
||||
for ((__i) = 0; \
|
||||
(__i) < (__state)->num_connector && \
|
||||
((connector) = (__state)->connectors[__i].ptr, \
|
||||
(old_connector_state) = (__state)->connectors[__i].old_state, 1); \
|
||||
(__i)++) \
|
||||
for_each_if (connector)
|
||||
(__i) < (__state)->num_connector; \
|
||||
(__i)++) \
|
||||
for_each_if ((__state)->connectors[__i].ptr && \
|
||||
((connector) = (__state)->connectors[__i].ptr, \
|
||||
(old_connector_state) = (__state)->connectors[__i].old_state, 1))
|
||||
|
||||
/**
|
||||
* 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);
|
|||
*/
|
||||
#define for_each_new_connector_in_state(__state, connector, new_connector_state, __i) \
|
||||
for ((__i) = 0; \
|
||||
(__i) < (__state)->num_connector && \
|
||||
((connector) = (__state)->connectors[__i].ptr, \
|
||||
(new_connector_state) = (__state)->connectors[__i].new_state, 1); \
|
||||
(__i)++) \
|
||||
for_each_if (connector)
|
||||
(__i) < (__state)->num_connector; \
|
||||
(__i)++) \
|
||||
for_each_if ((__state)->connectors[__i].ptr && \
|
||||
((connector) = (__state)->connectors[__i].ptr, \
|
||||
(new_connector_state) = (__state)->connectors[__i].new_state, 1))
|
||||
|
||||
/**
|
||||
* 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);
|
|||
*/
|
||||
#define for_each_oldnew_crtc_in_state(__state, crtc, old_crtc_state, new_crtc_state, __i) \
|
||||
for ((__i) = 0; \
|
||||
(__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) < (__state)->dev->mode_config.num_crtc; \
|
||||
(__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
|
||||
|
@ -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) \
|
||||
for ((__i) = 0; \
|
||||
(__i) < (__state)->dev->mode_config.num_crtc && \
|
||||
((crtc) = (__state)->crtcs[__i].ptr, \
|
||||
(old_crtc_state) = (__state)->crtcs[__i].old_state, 1); \
|
||||
(__i) < (__state)->dev->mode_config.num_crtc; \
|
||||
(__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
|
||||
|
@ -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) \
|
||||
for ((__i) = 0; \
|
||||
(__i) < (__state)->dev->mode_config.num_crtc && \
|
||||
((crtc) = (__state)->crtcs[__i].ptr, \
|
||||
(new_crtc_state) = (__state)->crtcs[__i].new_state, 1); \
|
||||
(__i) < (__state)->dev->mode_config.num_crtc; \
|
||||
(__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
|
||||
|
@ -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) \
|
||||
for ((__i) = 0; \
|
||||
(__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) < (__state)->dev->mode_config.num_total_plane; \
|
||||
(__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
|
||||
|
@ -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) \
|
||||
for ((__i) = 0; \
|
||||
(__i) < (__state)->dev->mode_config.num_total_plane && \
|
||||
((plane) = (__state)->planes[__i].ptr, \
|
||||
(old_plane_state) = (__state)->planes[__i].old_state, 1); \
|
||||
(__i) < (__state)->dev->mode_config.num_total_plane; \
|
||||
(__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
|
||||
* @__state: &struct drm_atomic_state pointer
|
||||
|
@ -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) \
|
||||
for ((__i) = 0; \
|
||||
(__i) < (__state)->dev->mode_config.num_total_plane && \
|
||||
((plane) = (__state)->planes[__i].ptr, \
|
||||
(new_plane_state) = (__state)->planes[__i].new_state, 1); \
|
||||
(__i) < (__state)->dev->mode_config.num_total_plane; \
|
||||
(__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
|
||||
|
@ -768,8 +767,7 @@ void drm_state_dump(struct drm_device *dev, struct drm_printer *p);
|
|||
((obj) = (__state)->private_objs[__i].ptr, \
|
||||
(old_obj_state) = (__state)->private_objs[__i].old_state, \
|
||||
(new_obj_state) = (__state)->private_objs[__i].new_state, 1); \
|
||||
(__i)++) \
|
||||
for_each_if (obj)
|
||||
(__i)++)
|
||||
|
||||
/**
|
||||
* 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);
|
|||
(__i) < (__state)->num_private_objs && \
|
||||
((obj) = (__state)->private_objs[__i].ptr, \
|
||||
(old_obj_state) = (__state)->private_objs[__i].old_state, 1); \
|
||||
(__i)++) \
|
||||
for_each_if (obj)
|
||||
(__i)++)
|
||||
|
||||
/**
|
||||
* 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);
|
|||
(__i) < (__state)->num_private_objs && \
|
||||
((obj) = (__state)->private_objs[__i].ptr, \
|
||||
(new_obj_state) = (__state)->private_objs[__i].new_state, 1); \
|
||||
(__i)++) \
|
||||
for_each_if (obj)
|
||||
(__i)++)
|
||||
|
||||
/**
|
||||
* 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,
|
|||
int port, int endpoint,
|
||||
struct drm_panel **panel,
|
||||
struct drm_bridge **bridge);
|
||||
int drm_of_panel_bridge_remove(const struct device_node *np,
|
||||
int port, int endpoint);
|
||||
#else
|
||||
static inline uint32_t drm_of_find_possible_crtcs(struct drm_device *dev,
|
||||
struct device_node *port)
|
||||
|
@ -65,6 +67,12 @@ static inline int drm_of_find_panel_or_bridge(const struct device_node *np,
|
|||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static inline int drm_of_panel_bridge_remove(const struct device_node *np,
|
||||
int port, int endpoint)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
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)
|
|||
struct dma_fence *fence;
|
||||
|
||||
fence = rcu_dereference(*fencep);
|
||||
if (!fence || !dma_fence_get_rcu(fence))
|
||||
if (!fence)
|
||||
return NULL;
|
||||
|
||||
if (!dma_fence_get_rcu(fence))
|
||||
continue;
|
||||
|
||||
/* The atomic_inc_not_zero() inside dma_fence_get_rcu()
|
||||
* provides a full memory barrier upon success (such as now).
|
||||
* This is paired with the write barrier from assigning
|
||||
|
|
|
@ -139,6 +139,45 @@ struct reg_sequence {
|
|||
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
|
||||
|
||||
enum regmap_endian {
|
||||
|
|
Loading…
Reference in New Issue
Block a user