diff options
29 files changed, 2602 insertions, 61 deletions
diff --git a/Documentation/devicetree/bindings/display/virtual-encoder.txt b/Documentation/devicetree/bindings/display/virtual-encoder.txt new file mode 100644 index 000000000000..3a9b8221c3ec --- /dev/null +++ b/Documentation/devicetree/bindings/display/virtual-encoder.txt @@ -0,0 +1,74 @@ +DRM Virtual Encoder + +The DRM Virtual Encoder is a component-based basic encoder that fetches +the display timings information from the device tree and "discovers" a +DRM output with the given data. It is helpful in a simulated environment +where there is no actual hardware to be probed and the configuration of +the display happens outside the kernel world. + +Required properties: + - compatible: should be "drm,virtual-encoder" + +Required sub-nodes: + - display-timings: node describing the virtual output timings information, + as specified in panel/display-timing.txt file. + - port: the input port connection as modelled using the OF graph bindings + specified in Documentation/devicetree/bindings/graph.txt + + +Example: + +/ { + ... + + vencoder { + compatible = "drm,virtual-encoder"; + display-timings { + native-mode = <&timing1>; + timing0: timing@0 { + /* 640x480 framebuffer */ + clock-frequency = <23750>; + hactive = <640>; + vactive = <480>; + hfront-porch = <48>; + hback-porch = <16>; + hsync-len = <96>; + vfront-porch = <33>; + vback-porch = <9>; + vsync-len = <3>; + }; + timing1: timing@1 { + /* 1280x720 framebuffer */ + clock-frequency = <74440000>; + hactive = <1280>; + vactive = <720>; + hfront-porch = <56>; + hback-porch = <192>; + hsync-len = <136>; + vfront-porch = <1>; + vback-porch = <22>; + vsync-len = <3>; + }; + }; + + port { + vencoder_in: endpoint { + remote-endpoint = <&driver_out>; + }; + }; + }; + + drm_driver: driver@f00bad { + ... + + port { + driver_out: endpoint { + remote-endpoint = <&vencoder_in>; + }; + }; + + ... + }; + + ... +}; diff --git a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi index 3086efacd00e..54939131af62 100644 --- a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi +++ b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi @@ -222,6 +222,9 @@ dvi-transmitter@39 { compatible = "sil,sii9022-tpi", "sil,sii9022"; reg = <0x39>; + + v2m_dvi_port: port { + }; }; dvi-transmitter@60 { @@ -245,7 +248,7 @@ reg-shift = <2>; }; - clcd@1f0000 { + v2m_clcd: clcd@1f0000 { compatible = "arm,pl111", "arm,primecell"; reg = <0x1f0000 0x1000>; interrupt-names = "combined"; diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts index 15f4fd3f4695..f8f2d391d303 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts @@ -57,6 +57,22 @@ interrupts = <0 85 4>; clocks = <&hdlcd_clk>; clock-names = "pxlclk"; + + port { + hdlcd0_output: endpoint@0 { + remote-endpoint = <&sii9022_0_input>; + }; + }; + }; + + hdmi0: connector@0 { + compatible = "hdmi-connector"; + type = "a"; + port { + hdmi0_connector_output: endpoint { + remote-endpoint = <&sii9022_0_output>; + }; + }; }; memory-controller@2b0a0000 { @@ -294,3 +310,17 @@ <0 3 &gic 0 39 4>; }; }; + +&v2m_clcd { + status = "disabled"; +}; + +&v2m_dvi_port { + sii9022_0_input: endpoint@0 { + remote-endpoint = <&hdlcd0_output>; + }; + + sii9022_0_output: endpoint@1 { + remote-endpoint = <&hdmi0_connector_output>; + }; +}; diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index bd107c5a0226..7773d7953322 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -116,6 +116,22 @@ interrupts = <0 85 4>; clocks = <&hdlcd_clk>; clock-names = "pxlclk"; + + port { + hdlcd0_output: endpoint@0 { + remote-endpoint = <&sii9022_0_input>; + }; + }; + }; + + hdmi0: connector@0 { + compatible = "hdmi-connector"; + type = "a"; + port { + hdmi0_connector_output: endpoint { + remote-endpoint = <&sii9022_0_output>; + }; + }; }; memory-controller@2b0a0000 { @@ -657,3 +673,17 @@ <0 3 &gic 0 39 4>; }; }; + +&v2m_clcd { + status = "disabled"; +}; + +&v2m_dvi_port { + sii9022_0_input: endpoint@0 { + remote-endpoint = <&hdlcd0_output>; + }; + + sii9022_0_output: endpoint@1 { + remote-endpoint = <&hdmi0_connector_output>; + }; +}; diff --git a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts index 1acecaf4b13d..6fad3c315c76 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts @@ -59,6 +59,22 @@ interrupts = <0 85 4>; clocks = <&hdlcd_clk>; clock-names = "pxlclk"; + + port { + hdlcd0_output: endpoint@0 { + remote-endpoint = <&sii9022_0_input>; + }; + }; + }; + + hdmi0: connector@0 { + compatible = "hdmi-connector"; + type = "a"; + port { + hdmi0_connector_output: endpoint { + remote-endpoint = <&sii9022_0_output>; + }; + }; }; memory-controller@2a150000 { @@ -264,3 +280,17 @@ <0 3 &gic 0 39 4>; }; }; + +&v2m_clcd { + status = "disabled"; +}; + +&v2m_dvi_port { + sii9022_0_input: endpoint@0 { + remote-endpoint = <&hdlcd0_output>; + }; + + sii9022_0_output: endpoint@1 { + remote-endpoint = <&hdmi0_connector_output>; + }; +}; diff --git a/arch/arm64/boot/dts/arm/juno-base.dtsi b/arch/arm64/boot/dts/arm/juno-base.dtsi index 7d832247d0db..d5b2b7a29a2b 100644 --- a/arch/arm64/boot/dts/arm/juno-base.dtsi +++ b/arch/arm64/boot/dts/arm/juno-base.dtsi @@ -1,3 +1,5 @@ +#include <dt-bindings/display/tda998x.h> + /* * Devices shared by all Juno boards */ @@ -443,13 +445,13 @@ compatible = "arm,scpi-dvfs-clocks"; #clock-cells = <1>; clock-indices = <0>, <1>, <2>; - clock-output-names = "atlclk", "aplclk","gpuclk"; + clock-output-names = "atlclk", "aplclk","clk_mali"; }; scpi_clk: scpi-clk { compatible = "arm,scpi-variable-clocks"; #clock-cells = <1>; - clock-indices = <3>; - clock-output-names = "pxlclk"; + clock-indices = <3>, <4>, <5>; + clock-output-names = "pxlclk", "pxlclk1", "i2sclk"; }; }; @@ -551,7 +553,7 @@ status = "disabled"; }; - dma@7ff00000 { + dma0: dma@7ff00000 { compatible = "arm,pl330", "arm,primecell"; reg = <0x0 0x7ff00000 0 0x1000>; #dma-cells = <1>; @@ -578,7 +580,7 @@ clocks = <&soc_faxiclk>; clock-names = "apb_pclk"; }; - +/* hdlcd@7ff50000 { compatible = "arm,hdlcd"; reg = <0 0x7ff50000 0 0x1000>; @@ -593,7 +595,7 @@ }; }; }; - +*/ hdlcd@7ff60000 { compatible = "arm,hdlcd"; reg = <0 0x7ff60000 0 0x1000>; @@ -627,16 +629,18 @@ i2c-sda-hold-time-ns = <500>; clocks = <&soc_smc50mhz>; - hdmi-transmitter@70 { + hdmi_transmitter0: hdmi-transmitter@70 { compatible = "nxp,tda998x"; reg = <0x70>; + #sound-dai-cells = <0>; + audio-ports = <TDA998x_I2S 0x03>; port { tda998x_0_input: tda998x-0-endpoint { remote-endpoint = <&hdlcd0_output>; }; }; }; - +/* hdmi-transmitter@71 { compatible = "nxp,tda998x"; reg = <0x71>; @@ -646,7 +650,7 @@ }; }; }; - }; +*/ }; ohci@7ffb0000 { compatible = "generic-ohci"; @@ -680,6 +684,35 @@ <0x00000008 0x80000000 0x1 0x80000000>; }; + soc_i2s: i2s@7ff90000 { + compatible = "snps,designware-i2s"; + reg = <0x0 0x7ff90000 0x0 0x1000>; + clocks = <&scpi_clk 5>, <&soc_refclk100mhz>; + clock-names = "i2sclk", "apb_pclk"; + #sound-dai-cells = <0>; + dmas = <&dma0 5>; + dma-names = "tx"; + }; + + hdmi_audio: hdmi_audio@0 { + compatible = "linux,hdmi-audio"; + #sound-dai-cells = <0>; + }; + + sound { + compatible = "simple-audio-card"; + + simple-audio-card,format = "i2s"; + + simple-audio-card,cpu { + sound-dai = <&soc_i2s>; + }; + + simple-audio-card,codec { + sound-dai = <&hdmi_transmitter0>; + }; + }; + smb@08000000 { compatible = "simple-bus"; #address-cells = <2>; diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index 1867f0d1389b..e86f7fcb71ba 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -47,6 +47,10 @@ config ARM_SCPI_POWER_DOMAIN This enables support for the SCPI power domains which can be enabled or disabled via the SCP firmware +config ARM_SCPI_PROTOCOL_TEST + tristate "Test code for SCPI Message Protocol" + default ARM_SCPI_PROTOCOL + config EDD tristate "BIOS Enhanced Disk Drive calls determine boot disk" depends on X86 diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index a37f12e8d137..f06daf68e68a 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_ARM_PSCI_FW) += psci.o obj-$(CONFIG_ARM_PSCI_CHECKER) += psci_checker.o obj-$(CONFIG_ARM_SCPI_PROTOCOL) += arm_scpi.o obj-$(CONFIG_ARM_SCPI_POWER_DOMAIN) += scpi_pm_domain.o +obj-$(CONFIG_ARM_SCPI_PROTOCOL_TEST) += arm_scpi_test.o obj-$(CONFIG_DMI) += dmi_scan.o obj-$(CONFIG_DMI_SYSFS) += dmi-sysfs.o obj-$(CONFIG_EDD) += edd.o diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index 9ad0b1934be9..d891ec00d3e9 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -87,8 +87,6 @@ #define FW_REV_MINOR(x) (((x) & FW_REV_MINOR_MASK) >> FW_REV_MINOR_BITS) #define FW_REV_PATCH(x) ((x) & FW_REV_PATCH_MASK) -#define MAX_RX_TIMEOUT (msecs_to_jiffies(30)) - enum scpi_error_codes { SCPI_SUCCESS = 0, /* Success */ SCPI_ERR_PARAM = 1, /* Invalid parameter(s) */ @@ -480,6 +478,18 @@ static void scpi_tx_prepare(struct mbox_client *c, void *msg) mem->command = cpu_to_le32(t->cmd); } +static void scpi_tx_done(struct mbox_client *c, void *msg, int result) +{ + struct scpi_xfer *t = msg; + + if (!t->rx_buf) + complete(&t->done); + /* + * Messages with rx_buf are expecting a reply and will be on the + * rx_pending list, so leave them alone. + */ +} + static struct scpi_xfer *get_scpi_xfer(struct scpi_chan *ch) { struct scpi_xfer *t; @@ -541,17 +551,24 @@ static int scpi_send_message(u8 idx, void *tx_buf, unsigned int tx_len, init_completion(&msg->done); ret = mbox_send_message(scpi_chan->chan, msg); - if (ret < 0 || !rx_buf) - goto out; + if (ret >= 0) { + /* + * Wait for message to be processed. If we end up having to wait + * for a very long time then there is a serious bug, probably in + * the firmware. + * + * IMPORTANT: We must not try and continue after the timeout + * because this driver and the mailbox framework still has data + * structures referring to the failed request and further + * serious bugs will result. + */ + if (!wait_for_completion_timeout(&msg->done, msecs_to_jiffies(10000))) + BUG(); - if (!wait_for_completion_timeout(&msg->done, MAX_RX_TIMEOUT)) - ret = -ETIMEDOUT; - else /* first status word */ - ret = msg->status; -out: - if (ret < 0 && rx_buf) /* remove entry from the list if timed-out */ - scpi_process_cmd(scpi_chan, msg->cmd); + if (rx_buf) + ret = le32_to_cpu(msg->status); + } put_scpi_xfer(msg, scpi_chan); /* SCPI error codes > 0, translate them to Linux scale*/ @@ -932,8 +949,7 @@ static int scpi_probe(struct platform_device *pdev) cl->dev = dev; cl->rx_callback = scpi_handle_remote_msg; cl->tx_prepare = scpi_tx_prepare; - cl->tx_block = true; - cl->tx_tout = 20; + cl->tx_done = scpi_tx_done; cl->knows_txdone = false; /* controller can't ack */ INIT_LIST_HEAD(&pchan->rx_pending); diff --git a/drivers/firmware/arm_scpi_test.c b/drivers/firmware/arm_scpi_test.c new file mode 100644 index 000000000000..c21b35e6f4b8 --- /dev/null +++ b/drivers/firmware/arm_scpi_test.c @@ -0,0 +1,568 @@ +/* + * Test code for System Control and Power Interface (SCPI) + * + * Copyright (C) 2015 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include <linux/module.h> +#include <linux/scpi_protocol.h> + + +static int stress_time; +module_param(stress_time, int, 0644); +MODULE_PARM_DESC(stress_time, "Number of seconds to run each stress test for, overides each test's default."); + +static int run; +static struct kernel_param_ops run_ops; +module_param_cb(run, &run_ops, &run, 0644); +MODULE_PARM_DESC(run, "The number of the test case to run, or -1 for all, 0 to stop tests."); + + +static struct scpi_ops *scpi; + +static struct task_struct *main_thread; + +static DEFINE_MUTEX(main_thread_lock); + + +#define MAX_TEST_THREADS 4 + +static struct test_thread { + struct task_struct *task; + int thread_num; +} test_threads[MAX_TEST_THREADS]; + +static DEFINE_MUTEX(thread_lock); + + +#define MAX_POWER_DOMAINS 8 + +static u16 num_sensors; +static u8 num_pd; +static u8 num_opps[MAX_POWER_DOMAINS]; +static struct mutex pd_lock[MAX_POWER_DOMAINS]; +static u8 num_devices_with_power_states; + + +#define FLAG_SERIAL_DVFS (1<<0) +#define FLAG_SERIAL_PD (1<<1) + +static int test_flags; + + +static int sensor_pmic = -1; + + +static u32 random_seed; + +static u32 random(u32 range) +{ + random_seed = random_seed*69069+1; + + return ((u64)random_seed * (u64)range) >> 32; +} + + +static atomic_t passes; +static atomic_t failures; + +static bool fail_on(bool fail) +{ + if (fail) + atomic_inc(&failures); + else + atomic_inc(&passes); + return fail; +} + +static void show_results(const char *title) +{ + int fail = atomic_xchg(&failures, 0); + int pass = atomic_xchg(&passes, 0); + + if (fail) + pr_err("Results for '%s' is %d/%d (pass/fail)\n", title, pass, fail); + else + pr_info("Results for '%s' is %d/%d (pass/fail)\n", title, pass, fail); +} + + +static bool check_name(const char *name) +{ + char c; + + if (!isalpha(*name++)) + return false; + + while ((c = *name++)) + if (!isalnum(c) && c != '_') + return false; + + return true; +} + +static u64 get_sensor(u16 id) +{ + u64 val; + int ret; + + ret = scpi->sensor_get_value(id, &val); + if (fail_on(ret < 0)) + pr_err("FAILED sensor_get_value %d (%d)\n", id, ret); + + return val; +} + +static void init_sensors(void) +{ + u16 id; + int ret; + + ret = scpi->sensor_get_capability(&num_sensors); + if (fail_on(ret)) + pr_err("FAILED sensor_get_capability (%d)\n", ret); + + pr_info("num_sensors: %d\n", num_sensors); + + for (id = 0; id < num_sensors; id++) { + + struct scpi_sensor_info info; + char name[sizeof(info.name) + 1]; + + ret = scpi->sensor_get_info(id, &info); + if (fail_on(ret)) { + pr_err("FAILED sensor_get_info (%d)\n", ret); + continue; + } + + /* Get sensor name, guarding against missing NUL terminator */ + memcpy(name, info.name, sizeof(info.name)); + name[sizeof(info.name)] = 0; + + pr_info("sensor[%d] id=%d class=%d trigger=%d name=%s\n", id, + info.sensor_id, info.class, info.trigger_type, name); + + if (fail_on(id != info.sensor_id)) + pr_err("FAILED bad sensor id\n"); + if (fail_on(info.class > 4)) + pr_err("FAILED bad sensor class\n"); + if (fail_on(info.trigger_type > 3)) + pr_err("FAILED bad sensor trigger type\n"); + if (fail_on(strlen(name) >= sizeof(info.name) || !check_name(name))) + pr_err("FAILED bad name\n"); + + pr_info("sensor[%d] value is %llu\n", id, get_sensor(id)); + if (strstr(name, "PMIC")) + sensor_pmic = id; + } +} + +static int get_dvfs(u8 pd) +{ + int ret = scpi->dvfs_get_idx(pd); + + if (fail_on(ret < 0)) + pr_err("FAILED get_dvfs %d (%d)\n", pd, ret); + else if (fail_on(ret >= num_opps[pd])) + pr_err("FAILED get_dvfs %d returned out of range index (%d)\n", pd, ret); + + return ret; +} + +static int set_dvfs(u8 pd, u8 opp) +{ + int ret; + + if (test_flags & FLAG_SERIAL_DVFS) + mutex_lock(&pd_lock[0]); + else if (test_flags & FLAG_SERIAL_PD) + mutex_lock(&pd_lock[pd]); + + ret = scpi->dvfs_set_idx(pd, opp); + + if (test_flags & FLAG_SERIAL_DVFS) + mutex_unlock(&pd_lock[0]); + else if (test_flags & FLAG_SERIAL_PD) + mutex_unlock(&pd_lock[pd]); + + if (fail_on(ret < 0)) + pr_err("FAILED set_dvfs %d %d (%d)\n", pd, opp, ret); + + return ret; +} + +static void init_dvfs(void) +{ + u8 pd; + + for (pd = 0; pd < MAX_POWER_DOMAINS; ++pd) { + struct scpi_dvfs_info *info; + int opp; + + info = scpi->dvfs_get_info(pd); + if (IS_ERR(info)) { + pr_info("dvfs_get_info %d failed with %d assume because no more power domains\n", + pd, (int)PTR_ERR(info)); + break; + } + + num_opps[pd] = info->count; + mutex_init(&pd_lock[pd]); + pr_info("pd[%d] count=%u latency=%u\n", + pd, info->count, info->latency); + + opp = get_dvfs(pd); + pr_info("pd[%d] current opp=%d\n", pd, opp); + + for (opp = 0; opp < info->count; ++opp) { + pr_info("pd[%d].opp[%d] freq=%u m_volt=%u\n", pd, opp, + info->opps[opp].freq, info->opps[opp].m_volt); + /* + * Try setting each opp. Note, failure is not necessarily + * an error because cpufreq may be setting values too. + */ + set_dvfs(pd, opp); + if (get_dvfs(pd) == opp) + pr_info("pd[%d] set to opp %d OK\n", pd, opp); + else + pr_warn("pd[%d] failed to set opp to %d\n", pd, opp); + } + } + + if (!pd) { + /* Assume device should have at least one DVFS power domain */ + pr_err("FAILED no power domains\n"); + fail_on(true); + } + num_pd = pd; +} + +static int device_get_power_state(u16 dev_id) +{ + int ret; + + ret = scpi->device_get_power_state(dev_id); + if (fail_on(ret < 0)) + pr_err("FAILED device_get_power_state %d (%d)\n", dev_id, ret); + + return ret; +} + +static int device_set_power_state(u16 dev_id, u8 pstate) +{ + int ret; + + ret = scpi->device_set_power_state(dev_id, pstate); + if (fail_on(ret < 0)) + pr_err("FAILED device_get_power_state %d (%d)\n", dev_id, ret); + + return ret; +} + +static void init_device_power_states(void) +{ + u16 dev_id; + + for (dev_id = 0; dev_id < 0xffff; ++dev_id) { + int state = scpi->device_get_power_state(dev_id); + + if (state < 0) { + pr_info("device_get_power_state %d failed with %d assume because no more devices\n", + dev_id, state); + break; + } + + pr_info("device[%d] current state=%d\n", dev_id, state); + + device_set_power_state(dev_id, state); + if (device_get_power_state(dev_id) == state) + pr_info("device[%d] set state to %d OK\n", dev_id, state); + else + pr_warn("device[%d] failed set state to %d\n", dev_id, state); + } + + if (!dev_id) { + /* Assume device should have at least one device power state */ + pr_err("FAILED no devices with power states\n"); + fail_on(true); + } + num_devices_with_power_states = dev_id; +} + +static int stress_pmic(void *data) +{ + int sensor, pd, opp; + + while (!kthread_should_stop()) { + sensor = sensor_pmic; + pd = random(num_pd); + opp = random(num_opps[pd]); + + switch (random(3)) { + case 0: + if (sensor >= 0) { + get_sensor(sensor); + break; + } + /* If no sensor, do DFVS... */ + case 1: + set_dvfs(pd, opp); + break; + default: + msleep(random(20)); + break; + } + } + + return 0; +} + +static int stress_all(void *data) +{ + int sensor, pd, opp; + + while (!kthread_should_stop()) { + sensor = random(num_sensors); + pd = random(num_pd); + opp = random(num_opps[pd]); + + switch (random(4)) { + case 0: + set_dvfs(pd, opp); + break; + case 1: + opp = get_dvfs(pd); + break; + case 2: + get_sensor(sensor); + break; + default: + msleep(random(20)); + break; + } + } + + return 0; +} + +struct test { + const char *title; + int (*thread_fn)(void *); + int flags; + int num_threads; + int duration; +}; + +static void stop_test_threads(void) +{ + int t, ret; + + for (t = 0; t < MAX_TEST_THREADS; ++t) { + struct test_thread *thread = &test_threads[t]; + + mutex_lock(&thread_lock); + if (thread->task) { + ret = kthread_stop(thread->task); + thread->task = NULL; + if (ret) + pr_warn("Test thread %d exited with status %d\n", t, ret); + } + mutex_unlock(&thread_lock); + } +} + +static void run_test(struct test *test) +{ + int num_threads = min(test->num_threads, MAX_TEST_THREADS); + int duration = stress_time; + int t; + + if (test->duration <= 0) + duration = 0; + else if (duration <= 0) + duration = test->duration; + + pr_info("Running test '%s' for %d seconds\n", test->title, duration); + + test_flags = test->flags; + + for (t = 0; t < num_threads; ++t) { + struct test_thread *thread = &test_threads[t]; + struct task_struct *task; + + mutex_lock(&thread_lock); + thread->thread_num = t; + task = kthread_run(test->thread_fn, thread, "scpi-test-%d", t); + if (IS_ERR(task)) + pr_warn("Failed to create test thread %d\n", t); + else + thread->task = task; + mutex_unlock(&thread_lock); + } + + schedule_timeout_interruptible(msecs_to_jiffies(duration * 1000)); + + stop_test_threads(); + + show_results(test->title); +} + +static struct test tests[] = { + {"Stress All, concurrent DVFS", + stress_all, 0, MAX_TEST_THREADS, 60}, + {"Stress All, concurrent DVFS on different PDs", + stress_all, FLAG_SERIAL_PD, MAX_TEST_THREADS, 60}, + {"Stress All, no concurrent DVFS", + stress_all, FLAG_SERIAL_DVFS, MAX_TEST_THREADS, 60}, + {"Stress PMIC, concurrent DVFS", + stress_pmic, 0, MAX_TEST_THREADS, 60}, + {"Stress PMIC, concurrent DVFS on different PDs", + stress_pmic, FLAG_SERIAL_PD, MAX_TEST_THREADS, 60}, + {"Stress PMIC, no concurrent DVFS", + stress_pmic, FLAG_SERIAL_DVFS, MAX_TEST_THREADS, 60}, + {} +}; + +static int main_thread_fn(void *data) +{ + struct test *test = tests; + int i = 1; + + for (; test->title && !kthread_should_stop(); ++test, ++i) + if (run < 0 || run == i) + run_test(test); + + run = 0; + return 0; +} + +static DEFINE_MUTEX(setup_lock); + +static int setup(void) +{ + int ret = 0; + + mutex_lock(&setup_lock); + + if (!scpi) { + int tries = 12; + + pr_info("Initial setup\n"); + while ((scpi = get_scpi_ops()) == 0 && --tries) { + pr_info("Waiting for get_scpi_ops\n"); + msleep(5000); + } + + if (scpi) { + init_sensors(); + init_dvfs(); + init_device_power_states(); + show_results("Initial setup"); + } else { + pr_err("Given up on get_scpi_ops\n"); + ret = -ENODEV; + } + } + + mutex_unlock(&setup_lock); + + return ret; +} + +static int start_tests(void) +{ + struct task_struct *task; + int ret; + + ret = setup(); + if (ret) { + run = 0; + return ret; + } + + pr_info("Creating main thread\n"); + mutex_lock(&main_thread_lock); + if (main_thread) { + ret = -EBUSY; + } else { + task = kthread_run(main_thread_fn, 0, "scpi-test-main"); + if (IS_ERR(task)) + ret = PTR_ERR(task); + else + main_thread = task; + } + mutex_unlock(&main_thread_lock); + + if (ret) { + pr_err("Failed to create main thread (%d)\n", ret); + run = 0; + } + + return ret; +} + +static void stop_tests(void) +{ + pr_info("Stopping tests\n"); + mutex_lock(&main_thread_lock); + if (main_thread) + kthread_stop(main_thread); + main_thread = NULL; + mutex_unlock(&main_thread_lock); +} + +static int param_set_running(const char *val, const struct kernel_param *kp) +{ + int ret; + + ret = param_set_int(val, kp); + if (!ret) { + if (run) + ret = start_tests(); + else + stop_tests(); + } + + return ret; +} + +static struct kernel_param_ops run_ops = { + .set = param_set_running, + .get = param_get_int, +}; + + +static int scpi_test_init(void) +{ + return 0; +} + +static void scpi_test_exit(void) +{ + stop_tests(); +} + +module_init(scpi_test_init); +module_exit(scpi_test_exit); + + +MODULE_AUTHOR("Jon Medhurst (Tixy) <tixy@linaro.org>"); +MODULE_DESCRIPTION("ARM SCPI driver tests"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index ebfe8404c25f..58fa2aa75979 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -175,6 +175,14 @@ config DRM_VGEM as used by Mesa's software renderer for enhanced performance. If M is selected the module will be called vgem. +config DRM_VIRT_ENCODER + tristate "Virtual OF-based encoder" + depends on DRM && OF + help + Choose this option to get a virtual encoder and its associated + connector that will use the device tree to read the display + timings information. If M is selected the module will be called + drm_vencoder. source "drivers/gpu/drm/exynos/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index b9ae4280de9d..165cc5f8d039 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -40,6 +40,9 @@ obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o CFLAGS_drm_trace_points.o := -I$(src) +drm_vencoder-y := drm_virtual_encoder.o +obj-$(CONFIG_DRM_VIRT_ENCODER) += drm_vencoder.o + obj-$(CONFIG_DRM) += drm.o obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o obj-$(CONFIG_DRM_ARM) += arm/ diff --git a/drivers/gpu/drm/arm/Makefile b/drivers/gpu/drm/arm/Makefile index bb8b158ff90d..0ade075b3568 100644 --- a/drivers/gpu/drm/arm/Makefile +++ b/drivers/gpu/drm/arm/Makefile @@ -1,4 +1,4 @@ -hdlcd-y := hdlcd_drv.o hdlcd_crtc.o +hdlcd-y := hdlcd_drv.o hdlcd_crtc.o hdlcd_fb_helper.o obj-$(CONFIG_DRM_HDLCD) += hdlcd.o mali-dp-y := malidp_drv.o malidp_hw.o malidp_planes.o malidp_crtc.o obj-$(CONFIG_DRM_MALI_DISPLAY) += mali-dp.o diff --git a/drivers/gpu/drm/arm/hdlcd_crtc.c b/drivers/gpu/drm/arm/hdlcd_crtc.c index 7d4e5aa77195..cdf5fe162465 100644 --- a/drivers/gpu/drm/arm/hdlcd_crtc.c +++ b/drivers/gpu/drm/arm/hdlcd_crtc.c @@ -14,7 +14,7 @@ #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_fb_helper.h> -#include <drm/drm_fb_cma_helper.h> +#include "hdlcd_fb_helper.h" #include <drm/drm_gem_cma_helper.h> #include <drm/drm_of.h> #include <drm/drm_plane_helper.h> @@ -88,16 +88,32 @@ static int hdlcd_set_pxl_fmt(struct drm_crtc *crtc) * pixel is outside the visible frame area or when there is a * buffer underrun. */ - hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, format->red.offset | + if(!IS_ENABLED(CONFIG_ARM)) { + hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, format->red.offset | #ifdef CONFIG_DRM_HDLCD_SHOW_UNDERRUN - 0x00ff0000 | /* show underruns in red */ + 0x00ff0000 | /* show underruns in red */ #endif - ((format->red.length & 0xf) << 8)); - hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, format->green.offset | - ((format->green.length & 0xf) << 8)); - hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, format->blue.offset | - ((format->blue.length & 0xf) << 8)); - + ((format->red.length & 0xf) << 8)); + hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, format->green.offset | + ((format->green.length & 0xf) << 8)); + hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, format->blue.offset | + ((format->blue.length & 0xf) << 8)); + } else { + /* + * This is a hack to swap read and blue when building for + * 32-bit ARM, because Versatile Express motherboard seems + * to be wired up differently. + */ + hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, format->red.offset | +#ifdef CONFIG_DRM_HDLCD_SHOW_UNDERRUN + 0x00ff0000 | /* show underruns in red */ +#endif + ((format->red.length & 0xf) << 8)); + hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, format->green.offset | + ((format->green.length & 0xf) << 8)); + hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, format->blue.offset | + ((format->blue.length & 0xf) << 8)); + } return 0; } @@ -232,7 +248,7 @@ static void hdlcd_plane_atomic_update(struct drm_plane *plane, src_h = plane->state->src_h >> 16; dest_w = plane->state->crtc_w; dest_h = plane->state->crtc_h; - gem = drm_fb_cma_get_gem_obj(plane->state->fb, 0); + gem = hdlcd_fb_get_gem_obj(plane->state->fb, 0); scanout_start = gem->paddr + plane->state->fb->offsets[0] + plane->state->crtc_y * plane->state->fb->pitches[0] + plane->state->crtc_x * diff --git a/drivers/gpu/drm/arm/hdlcd_drv.c b/drivers/gpu/drm/arm/hdlcd_drv.c index e5f4f4a6546d..6782fd9363a3 100644 --- a/drivers/gpu/drm/arm/hdlcd_drv.c +++ b/drivers/gpu/drm/arm/hdlcd_drv.c @@ -23,7 +23,7 @@ #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_fb_helper.h> -#include <drm/drm_fb_cma_helper.h> +#include "hdlcd_fb_helper.h" #include <drm/drm_gem_cma_helper.h> #include <drm/drm_of.h> @@ -67,6 +67,12 @@ static int hdlcd_load(struct drm_device *drm, unsigned long flags) (version & HDLCD_VERSION_MAJOR_MASK) >> 8, version & HDLCD_VERSION_MINOR_MASK); + /* Make sure hardware is in a safe reset state */ + hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 0); + hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, 0); + hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR,~0); + hdlcd_write(hdlcd, HDLCD_REG_INT_RAWSTAT, 0); + /* Get the optional framebuffer memory resource */ ret = of_reserved_mem_device_init(drm->dev); if (ret && ret != -ENODEV) @@ -88,6 +94,9 @@ static int hdlcd_load(struct drm_device *drm, unsigned long flags) goto irq_fail; } + spin_lock_init(&hdlcd->frame_completion_lock); + init_completion(&hdlcd->frame_completion); + return 0; irq_fail: @@ -102,11 +111,11 @@ static void hdlcd_fb_output_poll_changed(struct drm_device *drm) { struct hdlcd_drm_private *hdlcd = drm->dev_private; - drm_fbdev_cma_hotplug_event(hdlcd->fbdev); + hdlcd_drm_fbdev_hotplug_event(hdlcd->fbdev); } static const struct drm_mode_config_funcs hdlcd_mode_config_funcs = { - .fb_create = drm_fb_cma_create, + .fb_create = hdlcd_fb_create, .output_poll_changed = hdlcd_fb_output_poll_changed, .atomic_check = drm_atomic_helper_check, .atomic_commit = drm_atomic_helper_commit, @@ -126,7 +135,7 @@ static void hdlcd_lastclose(struct drm_device *drm) { struct hdlcd_drm_private *hdlcd = drm->dev_private; - drm_fbdev_cma_restore_mode(hdlcd->fbdev); + hdlcd_drm_fbdev_restore_mode(hdlcd->fbdev); } static irqreturn_t hdlcd_irq(int irq, void *arg) @@ -134,6 +143,7 @@ static irqreturn_t hdlcd_irq(int irq, void *arg) struct drm_device *drm = arg; struct hdlcd_drm_private *hdlcd = drm->dev_private; unsigned long irq_status; + unsigned long flags; irq_status = hdlcd_read(hdlcd, HDLCD_REG_INT_STATUS); @@ -154,6 +164,16 @@ static irqreturn_t hdlcd_irq(int irq, void *arg) if (irq_status & HDLCD_INTERRUPT_VSYNC) drm_crtc_handle_vblank(&hdlcd->crtc); + spin_lock_irqsave(&hdlcd->frame_completion_lock, flags); + if (hdlcd_read(hdlcd, HDLCD_REG_INT_STATUS) & HDLCD_INTERRUPT_DMA_END) { + /* Clear DMA_END interrupt here, under frame_completion_lock */ + hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, HDLCD_INTERRUPT_DMA_END); + irq_status &= ~HDLCD_INTERRUPT_DMA_END; + /* Wake up everyone waiting for frame completion */ + complete_all(&hdlcd->frame_completion); + } + spin_unlock_irqrestore(&hdlcd->frame_completion_lock, flags); + /* acknowledge interrupt(s) */ hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, irq_status); @@ -170,15 +190,18 @@ static void hdlcd_irq_preinstall(struct drm_device *drm) static int hdlcd_irq_postinstall(struct drm_device *drm) { -#ifdef CONFIG_DEBUG_FS struct hdlcd_drm_private *hdlcd = drm->dev_private; unsigned long irq_mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK); +#ifdef CONFIG_DEBUG_FS /* enable debug interrupts */ irq_mask |= HDLCD_DEBUG_INT_MASK; +#endif + /* enable DMA completion interrupts */ + irq_mask |= HDLCD_INTERRUPT_DMA_END; hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, irq_mask); -#endif + return 0; } @@ -193,12 +216,34 @@ static void hdlcd_irq_uninstall(struct drm_device *drm) irq_mask &= ~HDLCD_DEBUG_INT_MASK; #endif - /* disable vsync interrupts */ - irq_mask &= ~HDLCD_INTERRUPT_VSYNC; + /* disable vsync and dma interrupts */ + irq_mask &= ~(HDLCD_INTERRUPT_VSYNC | HDLCD_INTERRUPT_DMA_END); hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, irq_mask); } +void hdlcd_wait_for_frame_completion(struct drm_device *drm) +{ + struct hdlcd_drm_private *hdlcd = drm->dev_private; + + if (drm_crtc_vblank_get(&hdlcd->crtc)) + return; /* vblank interrupts not available so don't try and wait */ + + /* + * Clear pending interrupts and completions so we won't get signalled + * for any earlier frames, + */ + spin_lock_irq(&hdlcd->frame_completion_lock); + hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, HDLCD_INTERRUPT_DMA_END); + reinit_completion(&hdlcd->frame_completion); + spin_unlock_irq(&hdlcd->frame_completion_lock); + + /* Wait for end of current frame */ + wait_for_completion_interruptible_timeout(&hdlcd->frame_completion, HZ / 10); + + drm_crtc_vblank_put(&hdlcd->crtc); +} + static int hdlcd_enable_vblank(struct drm_device *drm, unsigned int crtc) { struct hdlcd_drm_private *hdlcd = drm->dev_private; @@ -247,7 +292,7 @@ static int hdlcd_show_pxlclock(struct seq_file *m, void *arg) static struct drm_info_list hdlcd_debugfs_list[] = { { "interrupt_count", hdlcd_show_underrun_count, 0 }, { "clocks", hdlcd_show_pxlclock, 0 }, - { "fb", drm_fb_cma_debugfs_show, 0 }, + { "fb", hdlcd_fb_debugfs_show, 0 }, }; static int hdlcd_debugfs_init(struct drm_minor *minor) @@ -318,6 +363,8 @@ static int hdlcd_drm_bind(struct device *dev) struct drm_device *drm; struct hdlcd_drm_private *hdlcd; int ret; + struct device_node *node; + int preferred_bpp; hdlcd = devm_kzalloc(dev, sizeof(*hdlcd), GFP_KERNEL); if (!hdlcd) @@ -356,7 +403,15 @@ static int hdlcd_drm_bind(struct device *dev) drm_mode_config_reset(drm); drm_kms_helper_poll_init(drm); - hdlcd->fbdev = drm_fbdev_cma_init(drm, 32, drm->mode_config.num_crtc, + /* Try to pick the colour depth that Android user-side is hard-coded for */ + preferred_bpp = 16; + node = of_find_compatible_node(NULL,NULL,"arm,mali-midgard"); + if (node) { + of_node_put(node); + preferred_bpp = 32; /* If Mali present, assume 32bpp */ + } + + hdlcd->fbdev = hdlcd_drm_fbdev_init(drm, preferred_bpp, drm->mode_config.num_crtc, drm->mode_config.num_connector); if (IS_ERR(hdlcd->fbdev)) { @@ -373,7 +428,7 @@ static int hdlcd_drm_bind(struct device *dev) err_register: if (hdlcd->fbdev) { - drm_fbdev_cma_fini(hdlcd->fbdev); + hdlcd_drm_fbdev_fini(hdlcd->fbdev); hdlcd->fbdev = NULL; } err_fbdev: @@ -401,7 +456,7 @@ static void hdlcd_drm_unbind(struct device *dev) drm_dev_unregister(drm); if (hdlcd->fbdev) { - drm_fbdev_cma_fini(hdlcd->fbdev); + hdlcd_drm_fbdev_fini(hdlcd->fbdev); hdlcd->fbdev = NULL; } drm_kms_helper_poll_fini(drm); @@ -519,7 +574,19 @@ static struct platform_driver hdlcd_platform_driver = { }, }; -module_platform_driver(hdlcd_platform_driver); +static int __init hdlcd_init(void) +{ + return platform_driver_register(&hdlcd_platform_driver); +} + +static void __exit hdlcd_exit(void) +{ + platform_driver_unregister(&hdlcd_platform_driver); +} + +/* need late_initcall() so we load after i2c driver */ +late_initcall(hdlcd_init); +module_exit(hdlcd_exit); MODULE_AUTHOR("Liviu Dudau"); MODULE_DESCRIPTION("ARM HDLCD DRM driver"); diff --git a/drivers/gpu/drm/arm/hdlcd_drv.h b/drivers/gpu/drm/arm/hdlcd_drv.h index e3950a071152..e76d99cfeada 100644 --- a/drivers/gpu/drm/arm/hdlcd_drv.h +++ b/drivers/gpu/drm/arm/hdlcd_drv.h @@ -8,10 +8,12 @@ struct hdlcd_drm_private { void __iomem *mmio; struct clk *clk; - struct drm_fbdev_cma *fbdev; + struct hdlcd_drm_fbdev *fbdev; struct drm_crtc crtc; struct drm_plane *plane; struct drm_atomic_state *state; + spinlock_t frame_completion_lock; + struct completion frame_completion; #ifdef CONFIG_DEBUG_FS atomic_t buffer_underrun_count; atomic_t bus_error_count; @@ -36,4 +38,6 @@ static inline u32 hdlcd_read(struct hdlcd_drm_private *hdlcd, unsigned int reg) int hdlcd_setup_crtc(struct drm_device *dev); void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd); +void hdlcd_wait_for_frame_completion(struct drm_device *drm); + #endif /* __HDLCD_DRV_H__ */ diff --git a/drivers/gpu/drm/arm/hdlcd_fb_helper.c b/drivers/gpu/drm/arm/hdlcd_fb_helper.c new file mode 100644 index 000000000000..fdf9baac78a7 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_fb_helper.c @@ -0,0 +1,930 @@ +/* + * drm kms/fb cma (contiguous memory allocator) helper functions + * + * Copyright (C) 2012 Analog Device Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Based on udl_fbdev.c + * Copyright (C) 2012 Red Hat + * + * 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. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_crtc.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include "hdlcd_fb_helper.h" +#include <linux/dma-buf.h> +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/reservation.h> + +#include "hdlcd_drv.h" +#include "hdlcd_regs.h" + +#define DEFAULT_FBDEFIO_DELAY_MS 50 + +#define MAX_FRAMES 2 + +static int hdlcd_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg); + +/****************************************************************************** + * Code copied from drivers/gpu/drm/drm_fb_helper.c as of Linux 4.4 + ******************************************************************************/ + +/** + * Copy of drm_fb_helper_check_var modified to allow MAX_FRAMES * height + */ +static int hdlcd_fb_helper_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct drm_fb_helper *fb_helper = info->par; + struct drm_framebuffer *fb = fb_helper->fb; + int depth; + + if (var->pixclock != 0 || in_dbg_master()) + return -EINVAL; + + /* Need to resize the fb object !!! */ + if (var->bits_per_pixel > fb->bits_per_pixel || + var->xres > fb->width || var->yres > fb->height || + var->xres_virtual > fb->width || var->yres_virtual > fb->height * MAX_FRAMES) { + DRM_DEBUG("fb userspace requested width/height/bpp is greater than current fb " + "request %dx%d-%d (virtual %dx%d) > %dx%d-%d\n", + var->xres, var->yres, var->bits_per_pixel, + var->xres_virtual, var->yres_virtual, + fb->width, fb->height, fb->bits_per_pixel); + return -EINVAL; + } + + switch (var->bits_per_pixel) { + case 16: + depth = (var->green.length == 6) ? 16 : 15; + break; + case 32: + depth = (var->transp.length > 0) ? 32 : 24; + break; + default: + depth = var->bits_per_pixel; + break; + } + + switch (depth) { + case 8: + var->red.offset = 0; + var->green.offset = 0; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 0; + var->transp.offset = 0; + break; + case 15: + var->red.offset = 10; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 5; + var->blue.length = 5; + var->transp.length = 1; + var->transp.offset = 15; + break; + case 16: + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + var->transp.length = 0; + var->transp.offset = 0; + break; + case 24: + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 0; + var->transp.offset = 0; + break; + case 32: + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 8; + var->transp.offset = 24; + break; + default: + return -EINVAL; + } + return 0; +} + +/****************************************************************************** + * Code copied from drivers/gpu/drm/drm_fb_cma_helper.c as of Linux 4.4 + ******************************************************************************/ + +struct hdlcd_fb { + struct drm_framebuffer fb; + struct drm_gem_cma_object *obj[4]; +}; + +struct hdlcd_drm_fbdev { + struct drm_fb_helper fb_helper; + struct hdlcd_fb *fb; +}; + +/** + * DOC: framebuffer cma helper functions + * + * Provides helper functions for creating a cma (contiguous memory allocator) + * backed framebuffer. + * + * hdlcd_fb_create() is used in the &drm_mode_config_funcs ->fb_create + * callback function to create a cma backed framebuffer. + * + * An fbdev framebuffer backed by cma is also available by calling + * hdlcd_drm_fbdev_init(). hdlcd_drm_fbdev_fini() tears it down. + * If the &drm_framebuffer_funcs ->dirty callback is set, fb_deferred_io + * will be set up automatically. dirty() is called by + * drm_fb_helper_deferred_io() in process context (struct delayed_work). + * + * Example fbdev deferred io code:: + * + * static int driver_fbdev_fb_dirty(struct drm_framebuffer *fb, + * struct drm_file *file_priv, + * unsigned flags, unsigned color, + * struct drm_clip_rect *clips, + * unsigned num_clips) + * { + * struct drm_gem_cma_object *cma = hdlcd_fb_get_gem_obj(fb, 0); + * ... push changes ... + * return 0; + * } + * + * static struct drm_framebuffer_funcs driver_fbdev_fb_funcs = { + * .destroy = hdlcd_fb_destroy, + * .create_handle = hdlcd_fb_create_handle, + * .dirty = driver_fbdev_fb_dirty, + * }; + * + * static int driver_fbdev_create(struct drm_fb_helper *helper, + * struct drm_fb_helper_surface_size *sizes) + * { + * return hdlcd_drm_fbdev_create_with_funcs(helper, sizes, + * &driver_fbdev_fb_funcs); + * } + * + * static const struct drm_fb_helper_funcs driver_fb_helper_funcs = { + * .fb_probe = driver_fbdev_create, + * }; + * + * Initialize: + * fbdev = hdlcd_drm_fbdev_init_with_funcs(dev, 16, + * dev->mode_config.num_crtc, + * dev->mode_config.num_connector, + * &driver_fb_helper_funcs); + * + */ + +static inline struct hdlcd_drm_fbdev *to_hdlcd_fbdev(struct drm_fb_helper *helper) +{ + return container_of(helper, struct hdlcd_drm_fbdev, fb_helper); +} + +static inline struct hdlcd_fb *to_hdlcd_fb(struct drm_framebuffer *fb) +{ + return container_of(fb, struct hdlcd_fb, fb); +} + +void hdlcd_fb_destroy(struct drm_framebuffer *fb) +{ + struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb); + int i; + + for (i = 0; i < 4; i++) { + if (hdlcd_fb->obj[i]) + drm_gem_object_unreference_unlocked(&hdlcd_fb->obj[i]->base); + } + + drm_framebuffer_cleanup(fb); + kfree(hdlcd_fb); +} +EXPORT_SYMBOL(hdlcd_fb_destroy); + +int hdlcd_fb_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, unsigned int *handle) +{ + struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb); + + return drm_gem_handle_create(file_priv, + &hdlcd_fb->obj[0]->base, handle); +} +EXPORT_SYMBOL(hdlcd_fb_create_handle); + +static struct drm_framebuffer_funcs hdlcd_fb_funcs = { + .destroy = hdlcd_fb_destroy, + .create_handle = hdlcd_fb_create_handle, +}; + +static struct hdlcd_fb *hdlcd_fb_alloc(struct drm_device *dev, + const struct drm_mode_fb_cmd2 *mode_cmd, + struct drm_gem_cma_object **obj, + unsigned int num_planes, const struct drm_framebuffer_funcs *funcs) +{ + struct hdlcd_fb *hdlcd_fb; + int ret; + int i; + + hdlcd_fb = kzalloc(sizeof(*hdlcd_fb), GFP_KERNEL); + if (!hdlcd_fb) + return ERR_PTR(-ENOMEM); + + drm_helper_mode_fill_fb_struct(&hdlcd_fb->fb, mode_cmd); + + for (i = 0; i < num_planes; i++) + hdlcd_fb->obj[i] = obj[i]; + + ret = drm_framebuffer_init(dev, &hdlcd_fb->fb, funcs); + if (ret) { + dev_err(dev->dev, "Failed to initialize framebuffer: %d\n", ret); + kfree(hdlcd_fb); + return ERR_PTR(ret); + } + + return hdlcd_fb; +} + +/** + * hdlcd_fb_create_with_funcs() - helper function for the + * &drm_mode_config_funcs ->fb_create + * callback function + * @dev: DRM device + * @file_priv: drm file for the ioctl call + * @mode_cmd: metadata from the userspace fb 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 + * dirty() callback. Use hdlcd_fb_create() if you don't need to change + * &drm_framebuffer_funcs. + */ +struct drm_framebuffer *hdlcd_fb_create_with_funcs(struct drm_device *dev, + struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd, + const struct drm_framebuffer_funcs *funcs) +{ + const struct drm_format_info *info; + struct hdlcd_fb *hdlcd_fb; + struct drm_gem_cma_object *objs[4]; + struct drm_gem_object *obj; + int ret; + int i; + + info = drm_format_info(mode_cmd->pixel_format); + if (!info) + return ERR_PTR(-EINVAL); + + for (i = 0; i < info->num_planes; i++) { + unsigned int width = mode_cmd->width / (i ? info->hsub : 1); + unsigned int height = mode_cmd->height / (i ? info->vsub : 1); + unsigned int min_size; + + obj = drm_gem_object_lookup(file_priv, mode_cmd->handles[i]); + if (!obj) { + dev_err(dev->dev, "Failed to lookup GEM object\n"); + ret = -ENXIO; + goto err_gem_object_unreference; + } + + min_size = (height - 1) * mode_cmd->pitches[i] + + width * info->cpp[i] + + mode_cmd->offsets[i]; + + if (obj->size < min_size) { + drm_gem_object_unreference_unlocked(obj); + ret = -EINVAL; + goto err_gem_object_unreference; + } + objs[i] = to_drm_gem_cma_obj(obj); + } + + hdlcd_fb = hdlcd_fb_alloc(dev, mode_cmd, objs, i, funcs); + if (IS_ERR(hdlcd_fb)) { + ret = PTR_ERR(hdlcd_fb); + goto err_gem_object_unreference; + } + + return &hdlcd_fb->fb; + +err_gem_object_unreference: + for (i--; i >= 0; i--) + drm_gem_object_unreference_unlocked(&objs[i]->base); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(hdlcd_fb_create_with_funcs); + +/** + * hdlcd_fb_create() - &drm_mode_config_funcs ->fb_create callback function + * @dev: DRM device + * @file_priv: drm file for the ioctl call + * @mode_cmd: metadata from the userspace fb creation request + * + * If your hardware has special alignment or pitch requirements these should be + * checked before calling this function. Use hdlcd_fb_create_with_funcs() if + * you need to set &drm_framebuffer_funcs ->dirty. + */ +struct drm_framebuffer *hdlcd_fb_create(struct drm_device *dev, + struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd) +{ + return hdlcd_fb_create_with_funcs(dev, file_priv, mode_cmd, + &hdlcd_fb_funcs); +} +EXPORT_SYMBOL_GPL(hdlcd_fb_create); + +/** + * hdlcd_fb_get_gem_obj() - Get CMA GEM object for framebuffer + * @fb: The framebuffer + * @plane: Which plane + * + * Return the CMA GEM object for given framebuffer. + * + * This function will usually be called from the CRTC callback functions. + */ +struct drm_gem_cma_object *hdlcd_fb_get_gem_obj(struct drm_framebuffer *fb, + unsigned int plane) +{ + struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb); + + if (plane >= 4) + return NULL; + + return hdlcd_fb->obj[plane]; +} +EXPORT_SYMBOL_GPL(hdlcd_fb_get_gem_obj); + +/** + * hdlcd_fb_prepare_fb() - Prepare CMA framebuffer + * @plane: Which plane + * @state: Plane state attach fence to + * + * This should be put into prepare_fb hook of struct &drm_plane_helper_funcs . + * + * 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. + * + * There is no need for cleanup_fb for CMA based framebuffer drivers. + */ +int hdlcd_fb_prepare_fb(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct dma_buf *dma_buf; + struct dma_fence *fence; + + if ((plane->state->fb == state->fb) || !state->fb) + return 0; + + dma_buf = hdlcd_fb_get_gem_obj(state->fb, 0)->base.dma_buf; + if (dma_buf) { + fence = reservation_object_get_excl_rcu(dma_buf->resv); + drm_atomic_set_fence_for_plane(state, fence); + } + + return 0; +} +EXPORT_SYMBOL_GPL(hdlcd_fb_prepare_fb); + +#ifdef CONFIG_DEBUG_FS +static void hdlcd_fb_describe(struct drm_framebuffer *fb, struct seq_file *m) +{ + struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb); + const struct drm_format_info *info; + int i; + + seq_printf(m, "fb: %dx%d@%4.4s\n", fb->width, fb->height, + (char *)&fb->pixel_format); + + info = drm_format_info(fb->pixel_format); + + for (i = 0; i < info->num_planes; i++) { + seq_printf(m, " %d: offset=%d pitch=%d, obj: ", + i, fb->offsets[i], fb->pitches[i]); + drm_gem_cma_describe(hdlcd_fb->obj[i], m); + } +} + +/** + * hdlcd_fb_debugfs_show() - Helper to list CMA framebuffer objects + * in debugfs. + * @m: output file + * @arg: private data for the callback + */ +int hdlcd_fb_debugfs_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct drm_framebuffer *fb; + + mutex_lock(&dev->mode_config.fb_lock); + drm_for_each_fb(fb, dev) + hdlcd_fb_describe(fb, m); + mutex_unlock(&dev->mode_config.fb_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(hdlcd_fb_debugfs_show); +#endif + +static int hdlcd_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + return dma_mmap_writecombine(info->device, vma, info->screen_base, + info->fix.smem_start, info->fix.smem_len); +} + +/* + * This function is copied from drivers/gpu/drm/drm_fourcc.c in Linux 4.9 + * (before it was deleted in 4.10 + */ +static void drm_fb_get_bpp_depth(uint32_t format, unsigned int *depth, + int *bpp) +{ + switch (format) { + case DRM_FORMAT_C8: + case DRM_FORMAT_RGB332: + case DRM_FORMAT_BGR233: + *depth = 8; + *bpp = 8; + break; + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_BGRX5551: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_BGRA5551: + *depth = 15; + *bpp = 16; + break; + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + *depth = 16; + *bpp = 16; + break; + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + *depth = 24; + *bpp = 24; + break; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRX8888: + *depth = 24; + *bpp = 32; + break; + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_XBGR2101010: + case DRM_FORMAT_RGBX1010102: + case DRM_FORMAT_BGRX1010102: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_BGRA1010102: + *depth = 30; + *bpp = 32; + break; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRA8888: + *depth = 32; + *bpp = 32; + break; + default: + *depth = 0; + *bpp = 0; + break; + } +} + +static int hdlcd_fb_helper_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct drm_fb_helper *helper = info->par; + struct drm_framebuffer *fb = helper->fb; + struct hdlcd_drm_private *hdlcd = helper->dev->dev_private; + unsigned int depth, bpp; + struct drm_gem_cma_object *gem; + dma_addr_t scanout_start; + int ret; + + ret = hdlcd_fb_helper_check_var(var, info); + if (ret) + return ret; + + drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp); + gem = hdlcd_fb_get_gem_obj(fb, 0); + + scanout_start = gem->paddr + fb->offsets[0] + + (var->yoffset * fb->pitches[0]) + (var->xoffset * bpp/8); + + hdlcd_write(hdlcd, HDLCD_REG_FB_BASE, scanout_start); + + hdlcd_wait_for_frame_completion(helper->dev); + + return 0; +} + +static struct fb_ops hdlcd_drm_fbdev_ops = { + .owner = THIS_MODULE, + DRM_FB_HELPER_DEFAULT_OPS, + .fb_fillrect = drm_fb_helper_sys_fillrect, + .fb_copyarea = drm_fb_helper_sys_copyarea, + .fb_imageblit = drm_fb_helper_sys_imageblit, + .fb_mmap = hdlcd_fb_mmap, + .fb_ioctl = hdlcd_fb_ioctl, + .fb_compat_ioctl= hdlcd_fb_ioctl, +}; + +static int hdlcd_drm_fbdev_deferred_io_mmap(struct fb_info *info, + struct vm_area_struct *vma) +{ + fb_deferred_io_mmap(info, vma); + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + return 0; +} + +static int hdlcd_drm_fbdev_defio_init(struct fb_info *fbi, + struct drm_gem_cma_object *cma_obj) +{ + struct fb_deferred_io *fbdefio; + struct fb_ops *fbops; + + /* + * Per device structures are needed because: + * fbops: fb_deferred_io_cleanup() clears fbops.fb_mmap + * fbdefio: individual delays + */ + fbdefio = kzalloc(sizeof(*fbdefio), GFP_KERNEL); + fbops = kzalloc(sizeof(*fbops), GFP_KERNEL); + if (!fbdefio || !fbops) { + kfree(fbdefio); + kfree(fbops); + return -ENOMEM; + } + + /* can't be offset from vaddr since dirty() uses cma_obj */ + fbi->screen_buffer = cma_obj->vaddr; + /* fb_deferred_io_fault() needs a physical address */ + fbi->fix.smem_start = page_to_phys(virt_to_page(fbi->screen_buffer)); + + *fbops = *fbi->fbops; + fbi->fbops = fbops; + + fbdefio->delay = msecs_to_jiffies(DEFAULT_FBDEFIO_DELAY_MS); + fbdefio->deferred_io = drm_fb_helper_deferred_io; + fbi->fbdefio = fbdefio; + fb_deferred_io_init(fbi); + fbi->fbops->fb_mmap = hdlcd_drm_fbdev_deferred_io_mmap; + + return 0; +} + +static void hdlcd_drm_fbdev_defio_fini(struct fb_info *fbi) +{ + if (!fbi->fbdefio) + return; + + fb_deferred_io_cleanup(fbi); + kfree(fbi->fbdefio); + kfree(fbi->fbops); +} + +/* + * For use in a (struct drm_fb_helper_funcs *)->fb_probe callback function that + * needs custom struct drm_framebuffer_funcs, like dirty() for deferred_io use. + */ +int hdlcd_drm_fbdev_create_with_funcs(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes, + const struct drm_framebuffer_funcs *funcs) +{ + struct hdlcd_drm_fbdev *hdlcd_fbdev = to_hdlcd_fbdev(helper); + struct drm_mode_fb_cmd2 mode_cmd = { 0 }; + struct drm_device *dev = helper->dev; + struct drm_gem_cma_object *obj; + struct drm_framebuffer *fb; + unsigned int bytes_per_pixel; + unsigned long offset; + struct fb_info *fbi; + size_t size; + int ret; + + DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d)\n", + sizes->surface_width, sizes->surface_height, + sizes->surface_bpp); + + bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8); + + mode_cmd.width = sizes->surface_width; + mode_cmd.height = sizes->surface_height; + mode_cmd.pitches[0] = sizes->surface_width * bytes_per_pixel; + mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, + sizes->surface_depth); + + size = mode_cmd.pitches[0] * mode_cmd.height * MAX_FRAMES; + obj = drm_gem_cma_create(dev, size); + if (IS_ERR(obj)) + return -ENOMEM; + + fbi = drm_fb_helper_alloc_fbi(helper); + if (IS_ERR(fbi)) { + ret = PTR_ERR(fbi); + goto err_gem_free_object; + } + + hdlcd_fbdev->fb = hdlcd_fb_alloc(dev, &mode_cmd, &obj, 1, funcs); + if (IS_ERR(hdlcd_fbdev->fb)) { + dev_err(dev->dev, "Failed to allocate DRM framebuffer.\n"); + ret = PTR_ERR(hdlcd_fbdev->fb); + goto err_fb_info_destroy; + } + + fb = &hdlcd_fbdev->fb->fb; + helper->fb = fb; + + fbi->par = helper; + fbi->flags = FBINFO_FLAG_DEFAULT; + hdlcd_drm_fbdev_ops.fb_check_var = hdlcd_fb_helper_check_var; + hdlcd_drm_fbdev_ops.fb_pan_display = hdlcd_fb_helper_pan_display; + fbi->fbops = &hdlcd_drm_fbdev_ops; + + drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth); + drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height); + + offset = fbi->var.xoffset * bytes_per_pixel; + offset += fbi->var.yoffset * fb->pitches[0]; + + dev->mode_config.fb_base = (resource_size_t)obj->paddr; + fbi->screen_base = obj->vaddr + offset; + fbi->fix.smem_start = (unsigned long)(obj->paddr + offset); + fbi->screen_size = size; + fbi->fix.smem_len = size; + fbi->var.yres_virtual = fbi->var.yres * MAX_FRAMES; + + if (funcs->dirty) { + ret = hdlcd_drm_fbdev_defio_init(fbi, obj); + if (ret) + goto err_cma_destroy; + } + + return 0; + +err_cma_destroy: + drm_framebuffer_unregister_private(&hdlcd_fbdev->fb->fb); + hdlcd_fb_destroy(&hdlcd_fbdev->fb->fb); +err_fb_info_destroy: + drm_fb_helper_release_fbi(helper); +err_gem_free_object: + drm_gem_object_unreference_unlocked(&obj->base); + return ret; +} +EXPORT_SYMBOL(hdlcd_drm_fbdev_create_with_funcs); + +static int hdlcd_drm_fbdev_create(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes) +{ + return hdlcd_drm_fbdev_create_with_funcs(helper, sizes, &hdlcd_fb_funcs); +} + +static const struct drm_fb_helper_funcs hdlcd_fb_helper_funcs = { + .fb_probe = hdlcd_drm_fbdev_create, +}; + +/** + * hdlcd_drm_fbdev_init_with_funcs() - Allocate and initializes a hdlcd_drm_fbdev struct + * @dev: DRM device + * @preferred_bpp: Preferred bits per pixel for the device + * @num_crtc: Number of CRTCs + * @max_conn_count: Maximum number of connectors + * @funcs: fb helper functions, in particular fb_probe() + * + * Returns a newly allocated hdlcd_drm_fbdev struct or a ERR_PTR. + */ +struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init_with_funcs(struct drm_device *dev, + unsigned int preferred_bpp, unsigned int num_crtc, + unsigned int max_conn_count, const struct drm_fb_helper_funcs *funcs) +{ + struct hdlcd_drm_fbdev *hdlcd_fbdev; + struct drm_fb_helper *helper; + int ret; + + hdlcd_fbdev = kzalloc(sizeof(*hdlcd_fbdev), GFP_KERNEL); + if (!hdlcd_fbdev) { + dev_err(dev->dev, "Failed to allocate drm fbdev.\n"); + return ERR_PTR(-ENOMEM); + } + + helper = &hdlcd_fbdev->fb_helper; + + drm_fb_helper_prepare(dev, helper, funcs); + + ret = drm_fb_helper_init(dev, helper, num_crtc, max_conn_count); + if (ret < 0) { + dev_err(dev->dev, "Failed to initialize drm fb helper.\n"); + goto err_free; + } + + ret = drm_fb_helper_single_add_all_connectors(helper); + if (ret < 0) { + dev_err(dev->dev, "Failed to add connectors.\n"); + goto err_drm_fb_helper_fini; + + } + + ret = drm_fb_helper_initial_config(helper, preferred_bpp); + if (ret < 0) { + dev_err(dev->dev, "Failed to set initial hw configuration.\n"); + goto err_drm_fb_helper_fini; + } + + return hdlcd_fbdev; + +err_drm_fb_helper_fini: + drm_fb_helper_fini(helper); +err_free: + kfree(hdlcd_fbdev); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_init_with_funcs); + +/** + * hdlcd_drm_fbdev_init() - Allocate and initializes a hdlcd_drm_fbdev struct + * @dev: DRM device + * @preferred_bpp: Preferred bits per pixel for the device + * @num_crtc: Number of CRTCs + * @max_conn_count: Maximum number of connectors + * + * Returns a newly allocated hdlcd_drm_fbdev struct or a ERR_PTR. + */ +struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init(struct drm_device *dev, + unsigned int preferred_bpp, unsigned int num_crtc, + unsigned int max_conn_count) +{ + return hdlcd_drm_fbdev_init_with_funcs(dev, preferred_bpp, num_crtc, + max_conn_count, &hdlcd_fb_helper_funcs); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_init); + +/** + * hdlcd_drm_fbdev_fini() - Free hdlcd_drm_fbdev struct + * @hdlcd_fbdev: The hdlcd_drm_fbdev struct + */ +void hdlcd_drm_fbdev_fini(struct hdlcd_drm_fbdev *hdlcd_fbdev) +{ + drm_fb_helper_unregister_fbi(&hdlcd_fbdev->fb_helper); + if (hdlcd_fbdev->fb_helper.fbdev) + hdlcd_drm_fbdev_defio_fini(hdlcd_fbdev->fb_helper.fbdev); + drm_fb_helper_release_fbi(&hdlcd_fbdev->fb_helper); + + if (hdlcd_fbdev->fb) { + drm_framebuffer_unregister_private(&hdlcd_fbdev->fb->fb); + hdlcd_fb_destroy(&hdlcd_fbdev->fb->fb); + } + + drm_fb_helper_fini(&hdlcd_fbdev->fb_helper); + kfree(hdlcd_fbdev); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_fini); + +/** + * hdlcd_drm_fbdev_restore_mode() - Restores initial framebuffer mode + * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL + * + * This function is usually called from the DRM drivers lastclose callback. + */ +void hdlcd_drm_fbdev_restore_mode(struct hdlcd_drm_fbdev *hdlcd_fbdev) +{ + if (hdlcd_fbdev) + drm_fb_helper_restore_fbdev_mode_unlocked(&hdlcd_fbdev->fb_helper); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_restore_mode); + +/** + * hdlcd_drm_fbdev_hotplug_event() - Poll for hotpulug events + * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL + * + * This function is usually called from the DRM drivers output_poll_changed + * callback. + */ +void hdlcd_drm_fbdev_hotplug_event(struct hdlcd_drm_fbdev *hdlcd_fbdev) +{ + if (hdlcd_fbdev) + drm_fb_helper_hotplug_event(&hdlcd_fbdev->fb_helper); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_hotplug_event); + +/** + * hdlcd_drm_fbdev_set_suspend - wrapper around drm_fb_helper_set_suspend + * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL + * @state: desired state, zero to resume, non-zero to suspend + * + * Calls drm_fb_helper_set_suspend, which is a wrapper around + * fb_set_suspend implemented by fbdev core. + */ +void hdlcd_drm_fbdev_set_suspend(struct hdlcd_drm_fbdev *hdlcd_fbdev, int state) +{ + if (hdlcd_fbdev) + drm_fb_helper_set_suspend(&hdlcd_fbdev->fb_helper, state); +} +EXPORT_SYMBOL(hdlcd_drm_fbdev_set_suspend); + +/****************************************************************************** + * IOCTL Interface + ******************************************************************************/ + +/* + * Used for sharing buffers with Mali userspace + */ +struct fb_dmabuf_export { + uint32_t fd; + uint32_t flags; +}; + +#define FBIOGET_DMABUF _IOR('F', 0x21, struct fb_dmabuf_export) + +static int hdlcd_get_dmabuf_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct fb_dmabuf_export ebuf; + struct drm_fb_helper *helper = info->par; + struct hdlcd_drm_private *hdlcd = helper->dev->dev_private; + struct drm_gem_cma_object *obj = hdlcd->fbdev->fb->obj[0]; + struct dma_buf *dma_buf; + uint32_t fd; + + if (copy_from_user(&ebuf, argp, sizeof(ebuf))) + return -EFAULT; + + /* + * We need a reference on the gem object. This will be released by + * drm_gem_dmabuf_release when the file descriptor is closed. + */ + drm_gem_object_reference(&obj->base); + + dma_buf = drm_gem_prime_export(helper->dev, &obj->base, ebuf.flags | O_RDWR); + if (!dma_buf) { + dev_info(info->dev, "Failed to export DMA buffer\n"); + goto err_export; + } + + fd = dma_buf_fd(dma_buf, O_CLOEXEC); + if (fd < 0) { + dev_info(info->dev, "Failed to get file descriptor for DMA buffer\n"); + goto err_export_fd; + } + ebuf.fd = fd; + + if (copy_to_user(argp, &ebuf, sizeof(ebuf))) + goto err_export_fd; + + return 0; + +err_export_fd: + dma_buf_put(dma_buf); +err_export: + drm_gem_object_unreference(&obj->base); + return -EFAULT; +} + +static int hdlcd_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case FBIOGET_DMABUF: + return hdlcd_get_dmabuf_ioctl(info, cmd, arg); + case FBIO_WAITFORVSYNC: + return 0; /* Nothing to do as we wait when page flipping anyway */ + default: + printk(KERN_INFO "HDLCD FB does not handle ioctl 0x%x\n", cmd); + } + + return -EFAULT; +} diff --git a/drivers/gpu/drm/arm/hdlcd_fb_helper.h b/drivers/gpu/drm/arm/hdlcd_fb_helper.h new file mode 100644 index 000000000000..dbc1372527fb --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_fb_helper.h @@ -0,0 +1,56 @@ +#ifndef __DRM_FB_CMA_HELPER_H__ +#define __DRM_FB_CMA_HELPER_H__ + +struct hdlcd_drm_fbdev; +struct drm_gem_cma_object; + +struct drm_fb_helper_surface_size; +struct drm_framebuffer_funcs; +struct drm_fb_helper_funcs; +struct drm_framebuffer; +struct drm_fb_helper; +struct drm_device; +struct drm_file; +struct drm_mode_fb_cmd2; +struct drm_plane; +struct drm_plane_state; + +struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init_with_funcs(struct drm_device *dev, + unsigned int preferred_bpp, unsigned int num_crtc, + unsigned int max_conn_count, const struct drm_fb_helper_funcs *funcs); +struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init(struct drm_device *dev, + unsigned int preferred_bpp, unsigned int num_crtc, + unsigned int max_conn_count); +void hdlcd_drm_fbdev_fini(struct hdlcd_drm_fbdev *hdlcd_fbdev); + +void hdlcd_drm_fbdev_restore_mode(struct hdlcd_drm_fbdev *hdlcd_fbdev); +void hdlcd_drm_fbdev_hotplug_event(struct hdlcd_drm_fbdev *hdlcd_fbdev); +void hdlcd_drm_fbdev_set_suspend(struct hdlcd_drm_fbdev *hdlcd_fbdev, int state); +int hdlcd_drm_fbdev_create_with_funcs(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes, + const struct drm_framebuffer_funcs *funcs); + +void hdlcd_fb_destroy(struct drm_framebuffer *fb); +int hdlcd_fb_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, unsigned int *handle); + +struct drm_framebuffer *hdlcd_fb_create_with_funcs(struct drm_device *dev, + struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd, + const struct drm_framebuffer_funcs *funcs); +struct drm_framebuffer *hdlcd_fb_create(struct drm_device *dev, + struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd); + +struct drm_gem_cma_object *hdlcd_fb_get_gem_obj(struct drm_framebuffer *fb, + unsigned int plane); + +int hdlcd_fb_prepare_fb(struct drm_plane *plane, + struct drm_plane_state *state); + +#ifdef CONFIG_DEBUG_FS +struct seq_file; + +int hdlcd_fb_debugfs_show(struct seq_file *m, void *arg); +#endif + +#endif + diff --git a/drivers/gpu/drm/drm_virtual_encoder.c b/drivers/gpu/drm/drm_virtual_encoder.c new file mode 100644 index 000000000000..21522ed9b17a --- /dev/null +++ b/drivers/gpu/drm/drm_virtual_encoder.c @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2016 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * Dummy encoder and connector that use the OF to "discover" the attached + * display timings. Can be used in situations where the encoder and connector's + * functionality are emulated and no setup steps are needed, or to describe + * attached panels for which no driver exists but can be used without + * additional hardware setup. + * + * The encoder also uses the component framework so that it can be a quick + * replacement for existing drivers when testing in an emulated environment. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_of.h> +#include <linux/component.h> +#include <video/display_timing.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + +struct drm_virt_priv { + struct drm_connector connector; + struct drm_encoder encoder; + struct display_timings *timings; +}; + +#define connector_to_drm_virt_priv(x) \ + container_of(x, struct drm_virt_priv, connector) + +#define encoder_to_drm_virt_priv(x) \ + container_of(x, struct drm_virt_priv, encoder) + +static void drm_virtcon_destroy(struct drm_connector *connector) +{ + struct drm_virt_priv *conn = connector_to_drm_virt_priv(connector); + + drm_connector_cleanup(connector); + display_timings_release(conn->timings); +} + +static enum drm_connector_status +drm_virtcon_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static const struct drm_connector_funcs drm_virtcon_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .reset = drm_atomic_helper_connector_reset, + .detect = drm_virtcon_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_virtcon_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int drm_virtcon_get_modes(struct drm_connector *connector) +{ + struct drm_virt_priv *conn = connector_to_drm_virt_priv(connector); + struct display_timings *timings = conn->timings; + int i; + + for (i = 0; i < timings->num_timings; i++) { + struct drm_display_mode *mode = drm_mode_create(connector->dev); + struct videomode vm; + + if (videomode_from_timings(timings, &vm, i)) + break; + + drm_display_mode_from_videomode(&vm, mode); + mode->type = DRM_MODE_TYPE_DRIVER; + if (timings->native_mode == i) + mode->type = DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + } + + return i; +} + +static int drm_virtcon_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +struct drm_encoder *drm_virtcon_best_encoder(struct drm_connector *connector) +{ + struct drm_virt_priv *priv = connector_to_drm_virt_priv(connector); + + return &priv->encoder; +} + +struct drm_encoder * +drm_virtcon_atomic_best_encoder(struct drm_connector *connector, + struct drm_connector_state *connector_state) +{ + struct drm_virt_priv *priv = connector_to_drm_virt_priv(connector); + + return &priv->encoder; +} + +static const struct drm_connector_helper_funcs drm_virtcon_helper_funcs = { + .get_modes = drm_virtcon_get_modes, + .mode_valid = drm_virtcon_mode_valid, + .best_encoder = drm_virtcon_best_encoder, + .atomic_best_encoder = drm_virtcon_atomic_best_encoder, +}; + +static void drm_vencoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs drm_vencoder_funcs = { + .destroy = drm_vencoder_destroy, +}; + +static void drm_vencoder_dpms(struct drm_encoder *encoder, int mode) +{ + /* nothing needed */ +} + +static bool drm_vencoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needed */ + return true; +} + +static void drm_vencoder_prepare(struct drm_encoder *encoder) +{ + drm_vencoder_dpms(encoder, DRM_MODE_DPMS_OFF); +} + +static void drm_vencoder_commit(struct drm_encoder *encoder) +{ + drm_vencoder_dpms(encoder, DRM_MODE_DPMS_ON); +} + +static void drm_vencoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needed */ +} + +static const struct drm_encoder_helper_funcs drm_vencoder_helper_funcs = { + .dpms = drm_vencoder_dpms, + .mode_fixup = drm_vencoder_mode_fixup, + .prepare = drm_vencoder_prepare, + .commit = drm_vencoder_commit, + .mode_set = drm_vencoder_mode_set, +}; + +static int drm_vencoder_bind(struct device *dev, struct device *master, + void *data) +{ + struct drm_encoder *encoder; + struct drm_virt_priv *con; + struct drm_connector *connector; + struct drm_device *drm = data; + u32 crtcs = 0; + int ret; + + con = devm_kzalloc(dev, sizeof(*con), GFP_KERNEL); + if (!con) + return -ENOMEM; + + dev_set_drvdata(dev, con); + connector = &con->connector; + encoder = &con->encoder; + + if (dev->of_node) { + struct drm_bridge *bridge; + crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); + bridge = of_drm_find_bridge(dev->of_node); + if (bridge) { + ret = drm_bridge_attach(drm, bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge\n"); + return ret; + } + encoder->bridge = bridge; + } + con->timings = of_get_display_timings(dev->of_node); + if (!con->timings) { + dev_err(dev, "failed to get display panel timings\n"); + return ENXIO; + } + } + + /* If no CRTCs were found, fall back to the old encoder's behaviour */ + if (crtcs == 0) { + dev_warn(dev, "Falling back to first CRTC\n"); + crtcs = 1 << 0; + } + + encoder->possible_crtcs = crtcs ? crtcs : 1; + encoder->possible_clones = 0; + + ret = drm_encoder_init(drm, encoder, &drm_vencoder_funcs, + DRM_MODE_ENCODER_VIRTUAL, "virtual-encoder"); + if (ret) + goto encoder_init_err; + + drm_encoder_helper_add(encoder, &drm_vencoder_helper_funcs); + + /* bogus values, pretend we're a 24" screen for DPI calculations */ + connector->display_info.width_mm = 519; + connector->display_info.height_mm = 324; + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + connector->polled = 0; + + ret = drm_connector_init(drm, connector, &drm_virtcon_funcs, + DRM_MODE_CONNECTOR_VIRTUAL); + if (ret) + goto connector_init_err; + + drm_connector_helper_add(connector, &drm_virtcon_helper_funcs); + + drm_connector_register(connector); + + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret) + goto attach_err; + + return ret; + +attach_err: + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +connector_init_err: + drm_encoder_cleanup(encoder); +encoder_init_err: + display_timings_release(con->timings); + + return ret; +}; + +static void drm_vencoder_unbind(struct device *dev, struct device *master, + void *data) +{ + struct drm_virt_priv *con = dev_get_drvdata(dev); + + drm_connector_unregister(&con->connector); + drm_connector_cleanup(&con->connector); + drm_encoder_cleanup(&con->encoder); + display_timings_release(con->timings); +} + +static const struct component_ops drm_vencoder_ops = { + .bind = drm_vencoder_bind, + .unbind = drm_vencoder_unbind, +}; + +static int drm_vencoder_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &drm_vencoder_ops); +} + +static int drm_vencoder_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &drm_vencoder_ops); + return 0; +} + +static const struct of_device_id drm_vencoder_of_match[] = { + { .compatible = "drm,virtual-encoder", }, + {}, +}; +MODULE_DEVICE_TABLE(of, drm_vencoder_of_match); + +static struct platform_driver drm_vencoder_driver = { + .probe = drm_vencoder_probe, + .remove = drm_vencoder_remove, + .driver = { + .name = "drm_vencoder", + .of_match_table = drm_vencoder_of_match, + }, +}; + +module_platform_driver(drm_vencoder_driver); + +MODULE_AUTHOR("Liviu Dudau"); +MODULE_DESCRIPTION("Virtual DRM Encoder"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index a6c92beb410a..23e0e8784e49 100644 --- a/drivers/gpu/drm/i2c/Kconfig +++ b/drivers/gpu/drm/i2c/Kconfig @@ -11,6 +11,9 @@ config DRM_I2C_CH7006 This driver is currently only useful if you're also using the nouveau driver. +config DRM_I2C_DUMMY + tristate "Dummy stub driver" + config DRM_I2C_SIL164 tristate "Silicon Image sil164 TMDS transmitter" default m if DRM_NOUVEAU diff --git a/drivers/gpu/drm/i2c/Makefile b/drivers/gpu/drm/i2c/Makefile index 43aa33baebed..658751730d50 100644 --- a/drivers/gpu/drm/i2c/Makefile +++ b/drivers/gpu/drm/i2c/Makefile @@ -3,6 +3,8 @@ ccflags-y := -Iinclude/drm ch7006-y := ch7006_drv.o ch7006_mode.o obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o +obj-$(CONFIG_DRM_I2C_DUMMY) += dummy_drm_i2c_drv.o + sil164-y := sil164_drv.o obj-$(CONFIG_DRM_I2C_SIL164) += sil164.o diff --git a/drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c b/drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c new file mode 100644 index 000000000000..2958f57940e8 --- /dev/null +++ b/drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c @@ -0,0 +1,264 @@ +/* + * This file was originally based on tda998x_drv.c which has the following + * copyright and licence... + * + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark <robdclark@gmail.com> + * + * 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. + * + * 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/component.h> +#include <linux/module.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder_slave.h> +#include <drm/drm_edid.h> +#include <drm/drm_of.h> + +struct dummy_priv { + struct drm_encoder encoder; + struct drm_connector connector; +}; + +#define conn_to_dummy_priv(x) \ + container_of(x, struct dummy_priv, connector); + +/* DRM encoder functions */ + +static void dummy_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool +dummy_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static int dummy_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static void +dummy_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ +} + +static enum drm_connector_status +dummy_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static const u8 edid_1024x768[] = { + /* + * These values are a copy of Documentation/EDID/1024x768.c + * produced by executing "make -C Documentation/EDID" + */ + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x31, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x16, 0x01, 0x03, 0x6d, 0x23, 0x1a, 0x78, + 0xea, 0x5e, 0xc0, 0xa4, 0x59, 0x4a, 0x98, 0x25, + 0x20, 0x50, 0x54, 0x00, 0x08, 0x00, 0x61, 0x40, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x64, 0x19, + 0x00, 0x40, 0x41, 0x00, 0x26, 0x30, 0x08, 0x90, + 0x36, 0x00, 0x63, 0x0a, 0x11, 0x00, 0x00, 0x18, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x4c, 0x69, 0x6e, + 0x75, 0x78, 0x20, 0x23, 0x30, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x3b, + 0x3d, 0x2f, 0x31, 0x07, 0x00, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfc, + 0x00, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x20, 0x58, + 0x47, 0x41, 0x0a, 0x20, 0x20, 0x20, 0x00, 0x55 +}; + +static int dummy_read_edid_block(void *data, u8 *buf, unsigned int blk, size_t length) +{ + memcpy(buf, edid_1024x768, min(sizeof(edid_1024x768),length)); + return 0; +} + +static int dummy_connector_get_modes(struct drm_connector *connector) +{ + struct edid *edid; + int n; + + edid = drm_do_get_edid(connector, dummy_read_edid_block, NULL); + if (!edid) + return 0; + + drm_mode_connector_update_edid_property(connector, edid); + n = drm_add_edid_modes(connector, edid); + kfree(edid); + + return n; +} + +/* I2C driver functions */ + +static void dummy_encoder_prepare(struct drm_encoder *encoder) +{ +} + +static void dummy_encoder_commit(struct drm_encoder *encoder) +{ +} + +static const struct drm_encoder_helper_funcs dummy_encoder_helper_funcs = { + .dpms = dummy_encoder_dpms, + .mode_fixup = dummy_encoder_mode_fixup, + .prepare = dummy_encoder_prepare, + .commit = dummy_encoder_commit, + .mode_set = dummy_encoder_mode_set, +}; + +static void dummy_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs dummy_encoder_funcs = { + .destroy = dummy_encoder_destroy, +}; + +static struct drm_encoder * +dummy_connector_best_encoder(struct drm_connector *connector) +{ + struct dummy_priv *priv = conn_to_dummy_priv(connector); + + return &priv->encoder; +} + +static +const struct drm_connector_helper_funcs dummy_connector_helper_funcs = { + .get_modes = dummy_connector_get_modes, + .mode_valid = dummy_connector_mode_valid, + .best_encoder = dummy_connector_best_encoder, +}; + +static void dummy_connector_destroy(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs dummy_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = dummy_connector_detect, + .destroy = dummy_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int dummy_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm = data; + struct dummy_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + + priv->connector.interlace_allowed = 1; + priv->encoder.possible_crtcs = 1 << 0; + + drm_encoder_helper_add(&priv->encoder, &dummy_encoder_helper_funcs); + ret = drm_encoder_init(drm, &priv->encoder, &dummy_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + if (ret) + goto err_encoder; + + drm_connector_helper_add(&priv->connector, + &dummy_connector_helper_funcs); + ret = drm_connector_init(drm, &priv->connector, + &dummy_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) + goto err_connector; + + drm_mode_connector_attach_encoder(&priv->connector, &priv->encoder); + + return 0; + +err_connector: + drm_encoder_cleanup(&priv->encoder); +err_encoder: + return ret; +} + +static void dummy_unbind(struct device *dev, struct device *master, + void *data) +{ + struct dummy_priv *priv = dev_get_drvdata(dev); + + drm_connector_cleanup(&priv->connector); + drm_encoder_cleanup(&priv->encoder); +} + +static const struct component_ops dummy_ops = { + .bind = dummy_bind, + .unbind = dummy_unbind, +}; + +static int +dummy_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + return component_add(&client->dev, &dummy_ops); +} + +static int dummy_remove(struct i2c_client *client) +{ + component_del(&client->dev, &dummy_ops); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id dummy_of_ids[] = { + { .compatible = "sil,sii9022-tpi", }, + { } +}; +MODULE_DEVICE_TABLE(of, dummy_of_ids); +#endif + +static struct i2c_device_id dummy_ids[] = { + { } +}; +MODULE_DEVICE_TABLE(i2c, dummy_ids); + +static struct i2c_driver dummy_driver = { + .probe = dummy_probe, + .remove = dummy_remove, + .driver = { + .name = "dummy_drm_i2c", + .of_match_table = dummy_of_ids + }, + .id_table = dummy_ids, +}; + +module_i2c_driver(dummy_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/marvell/sky2.c b/drivers/net/ethernet/marvell/sky2.c index b60ad0e56a9f..9e2c1fb463ec 100644 --- a/drivers/net/ethernet/marvell/sky2.c +++ b/drivers/net/ethernet/marvell/sky2.c @@ -101,6 +101,10 @@ static int legacy_pme = 0; module_param(legacy_pme, int, 0); MODULE_PARM_DESC(legacy_pme, "Legacy power management"); +/* Ugh! Let the firmware tell us the hardware address */ +static int mac_address[ETH_ALEN] = { 0, }; +module_param_array(mac_address, int, NULL, 0); + static const struct pci_device_id sky2_id_table[] = { { PCI_DEVICE(PCI_VENDOR_ID_SYSKONNECT, 0x9000) }, /* SK-9Sxx */ { PCI_DEVICE(PCI_VENDOR_ID_SYSKONNECT, 0x9E00) }, /* SK-9Exx */ @@ -3897,6 +3901,18 @@ static struct rtnl_link_stats64 *sky2_get_stats(struct net_device *dev, unsigned int start; u64 _bytes, _packets; + /* Try and check if device if off. If it is, abort gathering stats as + * any attempt to read hardware registers will generate a bus fault. + * This test is hacky and racy as there's nothing stopping the device + * being powered off immediately after the test. + */ + if (hw->pdev->pm_cap) { + u16 pmcsr; + int ret = pci_read_config_word(hw->pdev, hw->pdev->pm_cap + PCI_PM_CTRL, &pmcsr); + if (ret || (pmcsr & PCI_PM_CTRL_STATE_MASK) > PCI_D2) + return stats; /* Can't read power state or it's state D3 (off) */ + } + do { start = u64_stats_fetch_begin_irq(&sky2->rx_stats.syncp); _bytes = sky2->rx_stats.bytes; @@ -4809,13 +4825,21 @@ static struct net_device *sky2_init_netdev(struct sky2_hw *hw, unsigned port, /* try to get mac address in the following order: * 1) from device tree data * 2) from internal registers set by bootloader + * 3) from the command line parameter */ iap = of_get_mac_address(hw->pdev->dev.of_node); if (iap) memcpy(dev->dev_addr, iap, ETH_ALEN); - else + else { memcpy_fromio(dev->dev_addr, hw->regs + B2_MAC_1 + port * 8, ETH_ALEN); + if (!is_valid_ether_addr(&dev->dev_addr[0])) { + int i; + + for (i = 0; i < ETH_ALEN; i++) + dev->dev_addr[i] = mac_address[i]; + } + } /* if the address is invalid, use a random value */ if (!is_valid_ether_addr(dev->dev_addr)) { diff --git a/drivers/staging/android/ion/ion_dummy_driver.c b/drivers/staging/android/ion/ion_dummy_driver.c index cf5c010d32bc..9fffdce8ebbe 100644 --- a/drivers/staging/android/ion/ion_dummy_driver.c +++ b/drivers/staging/android/ion/ion_dummy_driver.c @@ -31,6 +31,11 @@ static struct ion_heap **heaps; static void *carveout_ptr; static void *chunk_ptr; +struct platform_device dummy_device_ion = { + .name = "ion-dummy", + .id = -1, +}; + static struct ion_platform_heap dummy_heaps[] = { { .id = ION_HEAP_TYPE_SYSTEM, @@ -56,6 +61,12 @@ static struct ion_platform_heap dummy_heaps[] = { .align = SZ_16K, .priv = (void *)(SZ_16K), }, + { + .id = ION_HEAP_TYPE_DMA, + .type = ION_HEAP_TYPE_DMA, + .name = "ion_dma_heap", + .priv = &dummy_device_ion.dev, + } }; static const struct ion_platform_data dummy_ion_pdata = { @@ -112,7 +123,9 @@ static int __init ion_dummy_init(void) } ion_device_add_heap(idev, heaps[i]); } - return 0; + + return platform_device_register(&dummy_device_ion); + err: for (i = 0; i < dummy_ion_pdata.nr; ++i) ion_heap_destroy(heaps[i]); diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 946672f97e1e..924dd17033df 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -831,6 +831,8 @@ int drm_crtc_force_disable_all(struct drm_device *dev); int drm_mode_set_config_internal(struct drm_mode_set *set); +extern int drm_create_virtual_connector(struct drm_device *dev); + /* Helpers */ static inline struct drm_crtc *drm_crtc_find(struct drm_device *dev, uint32_t id) diff --git a/linaro/configs/android.conf b/linaro/configs/android.conf index 637c36580a45..c9309874c1de 100644 --- a/linaro/configs/android.conf +++ b/linaro/configs/android.conf @@ -3,6 +3,7 @@ # CONFIG_INET_LRO is not set # CONFIG_MODULES is not set # CONFIG_OABI_COMPAT is not set +# CONFIG_SYSVIPC is not set CONFIG_ANDROID=y CONFIG_ANDROID_BINDER_IPC=y CONFIG_ANDROID_LOW_MEMORY_KILLER=y @@ -19,13 +20,16 @@ CONFIG_CGROUP_SCHED=y CONFIG_CP15_BARRIER_EMULATION=y CONFIG_DM_CRYPT=y CONFIG_DM_VERITY=y +CONFIG_DM_VERITY_FEC=y CONFIG_EMBEDDED=y CONFIG_FB=y +CONFIG_HARDENED_USERCOPY=y CONFIG_HIGH_RES_TIMERS=y CONFIG_INET6_AH=y CONFIG_INET6_ESP=y CONFIG_INET6_IPCOMP=y CONFIG_INET=y +CONFIG_INET_DIAG_DESTROY=y CONFIG_INET_ESP=y CONFIG_INET_XFRM_MODE_TUNNEL=y CONFIG_IP6_NF_FILTER=y @@ -33,7 +37,6 @@ CONFIG_IP6_NF_IPTABLES=y CONFIG_IP6_NF_MANGLE=y CONFIG_IP6_NF_RAW=y CONFIG_IP6_NF_TARGET_REJECT=y -CONFIG_IP6_NF_TARGET_REJECT_SKERR=y CONFIG_IPV6=y CONFIG_IPV6_MIP6=y CONFIG_IPV6_MULTIPLE_TABLES=y @@ -42,6 +45,7 @@ CONFIG_IPV6_PRIVACY=y CONFIG_IPV6_ROUTER_PREF=y CONFIG_IPV6_ROUTE_INFO=y CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTICAST=y CONFIG_IP_MULTIPLE_TABLES=y CONFIG_IP_NF_ARPFILTER=y CONFIG_IP_NF_ARPTABLES=y @@ -52,13 +56,13 @@ CONFIG_IP_NF_MANGLE=y CONFIG_IP_NF_MATCH_AH=y CONFIG_IP_NF_MATCH_ECN=y CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_NAT=y CONFIG_IP_NF_RAW=y CONFIG_IP_NF_SECURITY=y CONFIG_IP_NF_TARGET_MASQUERADE=y CONFIG_IP_NF_TARGET_NETMAP=y CONFIG_IP_NF_TARGET_REDIRECT=y CONFIG_IP_NF_TARGET_REJECT=y -CONFIG_IP_NF_TARGET_REJECT_SKERR=y CONFIG_NET=y CONFIG_NETDEVICES=y CONFIG_NETFILTER=y @@ -133,19 +137,26 @@ CONFIG_PPP_BSDCOMP=y CONFIG_PPP_DEFLATE=y CONFIG_PPP_MPPE=y CONFIG_PREEMPT=y -CONFIG_RESOURCE_COUNTERS=y +CONFIG_PROFILING=y +CONFIG_QFMT_V2=y +CONFIG_QUOTA=y +CONFIG_QUOTA_NETLINK_INTERFACE=y +CONFIG_QUOTA_TREE=y +CONFIG_QUOTACTL=y +CONFIG_RANDOMIZE_BASE=y CONFIG_RTC_CLASS=y CONFIG_RT_GROUP_SCHED=y +CONFIG_SECCOMP=y CONFIG_SECURITY=y CONFIG_SECURITY_NETWORK=y +CONFIG_SECURITY_PERF_EVENTS_RESTRICT=y CONFIG_SECURITY_SELINUX=y CONFIG_SETEND_EMULATION=y CONFIG_STAGING=y -CONFIG_SWITCH=y CONFIG_SWP_EMULATION=y CONFIG_SYNC=y -CONFIG_SYSVIPC=y CONFIG_TUN=y +CONFIG_UID_CPUTIME=y CONFIG_UNIX=y CONFIG_USB_GADGET=y CONFIG_USB_CONFIGFS=y @@ -155,8 +166,10 @@ CONFIG_USB_CONFIGFS_F_PTP=y CONFIG_USB_CONFIGFS_F_ACC=y CONFIG_USB_CONFIGFS_F_AUDIO_SRC=y CONFIG_USB_CONFIGFS_UEVENT=y +CONFIG_USB_CONFIGFS_F_MIDI=y CONFIG_USB_OTG_WAKELOCK=y CONFIG_XFRM_USER=y +# CONFIG_AIO is not set # CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set # CONFIG_INPUT_MOUSE is not set # CONFIG_LEGACY_PTYS is not set @@ -164,11 +177,14 @@ CONFIG_XFRM_USER=y # CONFIG_PM_WAKELOCKS_GC is not set # CONFIG_VT is not set CONFIG_ANDROID_TIMED_GPIO=y +CONFIG_ARM_KERNMEM_PERMS=y CONFIG_BACKLIGHT_LCD_SUPPORT=y CONFIG_BLK_DEV_LOOP=y CONFIG_BLK_DEV_RAM=y CONFIG_BLK_DEV_RAM_SIZE=8192 +CONFIG_CC_STACKPROTECTOR_STRONG=y CONFIG_COMPACTION=y +CONFIG_DEBUG_RODATA=y CONFIG_DM_UEVENT=y CONFIG_DRAGONRISE_FF=y CONFIG_ENABLE_DEFAULT_TRACERS=y @@ -266,14 +282,19 @@ CONFIG_TABLET_USB_AIPTEK=y CONFIG_TABLET_USB_GTCO=y CONFIG_TABLET_USB_HANWANG=y CONFIG_TABLET_USB_KBTAB=y -CONFIG_TABLET_USB_WACOM=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_TASK_XACCT=y CONFIG_TIMER_STATS=y CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y CONFIG_UHID=y -CONFIG_UID_STAT=y +CONFIG_MEMORY_STATE_TIME=y CONFIG_USB_ANNOUNCE_NEW_DEVICES=y CONFIG_USB_EHCI_HCD=y CONFIG_USB_HIDDEV=y CONFIG_USB_USBNET=y CONFIG_VFAT_FS=y +CONFIG_CPUSETS=y +CONFIG_PROC_PID_CPUSET=y diff --git a/linaro/configs/big-LITTLE-MP.conf b/linaro/configs/big-LITTLE-MP.conf new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/linaro/configs/big-LITTLE-MP.conf diff --git a/linaro/configs/vexpress.conf b/linaro/configs/vexpress.conf index 562212a29b6e..74b53428e7b0 100644 --- a/linaro/configs/vexpress.conf +++ b/linaro/configs/vexpress.conf @@ -14,10 +14,13 @@ CONFIG_ARM_BIG_LITTLE_CPUFREQ=y CONFIG_ARM_VEXPRESS_SPC_CPUFREQ=y CONFIG_PM_OPP=y CONFIG_CPU_FREQ=y -CONFIG_CPU_FREQ_GOV_ONDEMAND=y CONFIG_CPU_FREQ_GOV_PERFORMANCE=y -CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_INTERACTIVE=y +CONFIG_CPU_FREQ_DEFAULT_GOV_INTERACTIVE=y CONFIG_CMDLINE="console=ttyAMA0,38400n8 root=/dev/mmcblk0p2 rootwait mmci.fmax=4000000" +CONFIG_SECURITY_SELINUX_BOOTPARAM=y CONFIG_VFP=y CONFIG_NEON=y CONFIG_SCSI=y @@ -25,12 +28,22 @@ CONFIG_BLK_DEV_SD=y CONFIG_SMSC911X=y CONFIG_SMC91X=y CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_MOUSE=y +CONFIG_MOUSE_PS2=y CONFIG_SERIO_AMBAKMI=y CONFIG_SERIAL_AMBA_PL011=y CONFIG_SERIAL_AMBA_PL011_CONSOLE=y CONFIG_FB=y CONFIG_FB_ARMCLCD=y -CONFIG_FB_ARMHDLCD=y +CONFIG_DRM=y +CONFIG_DRM_ARM=y +CONFIG_DRM_HDLCD=y +CONFIG_CMA=y +CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_SEL_MBYTES=y +CONFIG_CMA_SIZE_MBYTES=32 +CONFIG_I2C_VERSATILE=y +CONFIG_DRM_I2C_DUMMY=y CONFIG_LOGO=y # CONFIG_LOGO_LINUX_MONO is not set # CONFIG_LOGO_LINUX_VGA16 is not set diff --git a/linaro/configs/vexpress64.conf b/linaro/configs/vexpress64.conf index 1eafbc7f0cce..d5ae380d9b6f 100644 --- a/linaro/configs/vexpress64.conf +++ b/linaro/configs/vexpress64.conf @@ -1,12 +1,14 @@ CONFIG_ARCH_VEXPRESS=y CONFIG_SMP=y -CONFIG_NR_CPUS=8 CONFIG_HOTPLUG_CPU=y CONFIG_PREEMPT=y CONFIG_CMDLINE="console=ttyAMA0" +CONFIG_SECURITY_SELINUX_BOOTPARAM=y CONFIG_COMPAT=y CONFIG_SMC91X=y CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_MOUSE=y +CONFIG_MOUSE_PS2=y CONFIG_SERIO_AMBAKMI=y CONFIG_SERIAL_AMBA_PL011=y CONFIG_SERIAL_AMBA_PL011_CONSOLE=y @@ -74,3 +76,28 @@ CONFIG_CONNECTOR=y CONFIG_ATA=y CONFIG_SATA_SIL24=y CONFIG_SKY2=y +CONFIG_ARM_TIMER_SP804=y +CONFIG_ARM_CPUIDLE=y +CONFIG_DRM=y +CONFIG_DRM_ARM=y +CONFIG_DRM_HDLCD=y +CONFIG_DRM_VIRT_ENCODER=y +CONFIG_CMA=y +CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_SEL_MBYTES=y +CONFIG_CMA_SIZE_MBYTES=32 +CONFIG_DRM_I2C_NXP_TDA998X=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_SEQUENCER=y +CONFIG_SND_SEQ_DUMMY=y +CONFIG_SND_MIXER_OSS=y +CONFIG_SND_PCM_OSS=y +CONFIG_SND_SEQUENCER_OSS=y +# CONFIG_SND_USB is not set +CONFIG_SND_SOC=y +CONFIG_SND_DESIGNWARE_I2S=y +CONFIG_SND_SOC_SPDIF=y +CONFIG_SND_SIMPLE_CARD=y +CONFIG_DMADEVICES=y +CONFIG_PL330_DMA=y |