drm/tegra: Changes for v3.20-rc1
The biggest part of these changes is the conversion to atomic mode- setting. A lot of cleanup and demidlayering was required before the conversion, with the result being a whole lot of changes. Besides the atomic mode-setting support, the host1x bus now has the proper infrastructure to support suspend/resume for child devices. Finally, a couple of smaller cleanup patches round things off. -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIcBAABAgAGBQJUx1hgAAoJEN0jrNd/PrOhpOIQAI7YAJUX7465R59vkjXOV3uP lGMj75v22Wq2Y3poPNyxnn562W0feKVBPf13oWgZvzK1fk3+eXoVmL2F395VYXjz rsGhJJe+/z8gtBgNXH0l6jzvQwRHzLCoMpt9Egrzbij6S+BShQZvnwg5Gyws6vBe ZnpTjxJKZD4ADY5G1J/EFmBAW3B3abZqOLGHohbUSeN/rr3VP92eG7vnPDW8Rrry UuBJkjdkR49cfnEvz4pCEoiucftIja7y0YhkDy6estPnO3JBDQ01tCI6HvA6CT1k 6wCkWOnPXJ1Uy1OcXgMU59azJ7d8wdwXFWCXc5yWwIj47ZmY6OjwyJfURmrlQ6nb 8lxeedCBgmTLF6sB9g8g117HWCM3InUXOeN4EhkcF6el9X+ZOAlIJO2RllfL8Bwn O0GXgCmeeeoZG6aLpULEnAZhJZ92nvo2Zn5lddk7uiaU09Xdzc4kKN3FzYS0XtfJ 7A32m/DNPQi9yJnFFK2sWrrSBnHcDd0Cq17/V1VTcP6iV3CtRWIDX7co0QHE+EQl 7xjj2rlFEyWcvD+9pUoYEgDDCuzjy41zCQ53vj+h2xDO6maksqT1vJ1Nz8y9JmQI F52aU+DyHleP3q0u9CmeFIo1ft5ho7IgzPJXHPqir/QcnxqiqDprEM70bQwVdF+y iVvngQ3xfkH2RaqOwen0 =k0/3 -----END PGP SIGNATURE----- Merge tag 'drm/tegra/for-3.20-rc1' of git://anongit.freedesktop.org/tegra/linux into drm-next drm/tegra: Changes for v3.20-rc1 The biggest part of these changes is the conversion to atomic mode- setting. A lot of cleanup and demidlayering was required before the conversion, with the result being a whole lot of changes. Besides the atomic mode-setting support, the host1x bus now has the proper infrastructure to support suspend/resume for child devices. Finally, a couple of smaller cleanup patches round things off. * tag 'drm/tegra/for-3.20-rc1' of git://anongit.freedesktop.org/tegra/linux: (54 commits) drm/tegra: Use correct relocation target offsets drm/tegra: Add minimal power management drm/tegra: dc: Unify enabling the display controller drm/tegra: Track tiling and format in plane state drm/tegra: Track active planes in CRTC state drm/tegra: Remove unused ->mode_fixup() callbacks drm/tegra: Atomic conversion, phase 3, step 3 drm/tegra: Atomic conversion, phase 3, step 2 drm/tegra: dc: Use atomic clock state in modeset drm/tegra: sor: Implement ->atomic_check() drm/tegra: hdmi: Implement ->atomic_check() drm/tegra: dsi: Implement ->atomic_check() drm/tegra: rgb: Implement ->atomic_check() drm/tegra: dc: Store clock setup in atomic state drm/tegra: Atomic conversion, phase 3, step 1 drm/tegra: Atomic conversion, phase 2 drm/tegra: Atomic conversion, phase 1 drm/tegra: dc: Do not needlessly deassert reset drm/tegra: Output cleanup functions cannot fail drm/tegra: Remove remnants of the output midlayer ...
This commit is contained in:
commit
2f5b4ef15c
|
@ -1,3 +1,6 @@
|
|||
obj-y += drm/ vga/
|
||||
# drm/tegra depends on host1x, so if both drivers are built-in care must be
|
||||
# taken to initialize them in the correct order. Link order is the only way
|
||||
# to ensure this currently.
|
||||
obj-$(CONFIG_TEGRA_HOST1X) += host1x/
|
||||
obj-y += drm/ vga/
|
||||
obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/
|
||||
|
|
|
@ -297,13 +297,22 @@ mode_fixup(struct drm_atomic_state *state)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
ret = funcs->mode_fixup(encoder, &crtc_state->mode,
|
||||
&crtc_state->adjusted_mode);
|
||||
if (!ret) {
|
||||
DRM_DEBUG_KMS("[ENCODER:%d:%s] fixup failed\n",
|
||||
encoder->base.id, encoder->name);
|
||||
return -EINVAL;
|
||||
if (funcs->atomic_check) {
|
||||
ret = funcs->atomic_check(encoder, crtc_state,
|
||||
conn_state);
|
||||
if (ret) {
|
||||
DRM_DEBUG_KMS("[ENCODER:%d:%s] check failed\n",
|
||||
encoder->base.id, encoder->name);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
ret = funcs->mode_fixup(encoder, &crtc_state->mode,
|
||||
&crtc_state->adjusted_mode);
|
||||
if (!ret) {
|
||||
DRM_DEBUG_KMS("[ENCODER:%d:%s] fixup failed\n",
|
||||
encoder->base.id, encoder->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1108,12 +1117,19 @@ void drm_atomic_helper_commit_planes(struct drm_device *dev,
|
|||
|
||||
funcs = plane->helper_private;
|
||||
|
||||
if (!funcs || !funcs->atomic_update)
|
||||
if (!funcs)
|
||||
continue;
|
||||
|
||||
old_plane_state = old_state->plane_states[i];
|
||||
|
||||
funcs->atomic_update(plane, old_plane_state);
|
||||
/*
|
||||
* Special-case disabling the plane if drivers support it.
|
||||
*/
|
||||
if (drm_atomic_plane_disabling(plane, old_plane_state) &&
|
||||
funcs->atomic_disable)
|
||||
funcs->atomic_disable(plane, old_plane_state);
|
||||
else
|
||||
funcs->atomic_update(plane, old_plane_state);
|
||||
}
|
||||
|
||||
for (i = 0; i < ncrtcs; i++) {
|
||||
|
|
|
@ -449,7 +449,15 @@ int drm_plane_helper_commit(struct drm_plane *plane,
|
|||
crtc_funcs[i]->atomic_begin(crtc[i]);
|
||||
}
|
||||
|
||||
plane_funcs->atomic_update(plane, plane_state);
|
||||
/*
|
||||
* Drivers may optionally implement the ->atomic_disable callback, so
|
||||
* special-case that here.
|
||||
*/
|
||||
if (drm_atomic_plane_disabling(plane, plane_state) &&
|
||||
plane_funcs->atomic_disable)
|
||||
plane_funcs->atomic_disable(plane, plane_state);
|
||||
else
|
||||
plane_funcs->atomic_update(plane, plane_state);
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (crtc_funcs[i] && crtc_funcs[i]->atomic_flush)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -10,6 +10,9 @@
|
|||
#include <linux/host1x.h>
|
||||
#include <linux/iommu.h>
|
||||
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
|
||||
#include "drm.h"
|
||||
#include "gem.h"
|
||||
|
||||
|
@ -24,6 +27,92 @@ struct tegra_drm_file {
|
|||
struct list_head contexts;
|
||||
};
|
||||
|
||||
static void tegra_atomic_schedule(struct tegra_drm *tegra,
|
||||
struct drm_atomic_state *state)
|
||||
{
|
||||
tegra->commit.state = state;
|
||||
schedule_work(&tegra->commit.work);
|
||||
}
|
||||
|
||||
static void tegra_atomic_complete(struct tegra_drm *tegra,
|
||||
struct drm_atomic_state *state)
|
||||
{
|
||||
struct drm_device *drm = tegra->drm;
|
||||
|
||||
/*
|
||||
* Everything below can be run asynchronously without the need to grab
|
||||
* any modeset locks at all under one condition: It must be guaranteed
|
||||
* that the asynchronous work has either been cancelled (if the driver
|
||||
* supports it, which at least requires that the framebuffers get
|
||||
* cleaned up with drm_atomic_helper_cleanup_planes()) or completed
|
||||
* before the new state gets committed on the software side with
|
||||
* drm_atomic_helper_swap_state().
|
||||
*
|
||||
* This scheme allows new atomic state updates to be prepared and
|
||||
* checked in parallel to the asynchronous completion of the previous
|
||||
* update. Which is important since compositors need to figure out the
|
||||
* composition of the next frame right after having submitted the
|
||||
* current layout.
|
||||
*/
|
||||
|
||||
drm_atomic_helper_commit_pre_planes(drm, state);
|
||||
drm_atomic_helper_commit_planes(drm, state);
|
||||
drm_atomic_helper_commit_post_planes(drm, state);
|
||||
|
||||
drm_atomic_helper_wait_for_vblanks(drm, state);
|
||||
|
||||
drm_atomic_helper_cleanup_planes(drm, state);
|
||||
drm_atomic_state_free(state);
|
||||
}
|
||||
|
||||
static void tegra_atomic_work(struct work_struct *work)
|
||||
{
|
||||
struct tegra_drm *tegra = container_of(work, struct tegra_drm,
|
||||
commit.work);
|
||||
|
||||
tegra_atomic_complete(tegra, tegra->commit.state);
|
||||
}
|
||||
|
||||
static int tegra_atomic_commit(struct drm_device *drm,
|
||||
struct drm_atomic_state *state, bool async)
|
||||
{
|
||||
struct tegra_drm *tegra = drm->dev_private;
|
||||
int err;
|
||||
|
||||
err = drm_atomic_helper_prepare_planes(drm, state);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* serialize outstanding asynchronous commits */
|
||||
mutex_lock(&tegra->commit.lock);
|
||||
flush_work(&tegra->commit.work);
|
||||
|
||||
/*
|
||||
* This is the point of no return - everything below never fails except
|
||||
* when the hw goes bonghits. Which means we can commit the new state on
|
||||
* the software side now.
|
||||
*/
|
||||
|
||||
drm_atomic_helper_swap_state(drm, state);
|
||||
|
||||
if (async)
|
||||
tegra_atomic_schedule(tegra, state);
|
||||
else
|
||||
tegra_atomic_complete(tegra, state);
|
||||
|
||||
mutex_unlock(&tegra->commit.lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct drm_mode_config_funcs tegra_drm_mode_funcs = {
|
||||
.fb_create = tegra_fb_create,
|
||||
#ifdef CONFIG_DRM_TEGRA_FBDEV
|
||||
.output_poll_changed = tegra_fb_output_poll_changed,
|
||||
#endif
|
||||
.atomic_check = drm_atomic_helper_check,
|
||||
.atomic_commit = tegra_atomic_commit,
|
||||
};
|
||||
|
||||
static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
|
||||
{
|
||||
struct host1x_device *device = to_host1x_device(drm->dev);
|
||||
|
@ -36,8 +125,8 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
|
|||
|
||||
if (iommu_present(&platform_bus_type)) {
|
||||
tegra->domain = iommu_domain_alloc(&platform_bus_type);
|
||||
if (IS_ERR(tegra->domain)) {
|
||||
err = PTR_ERR(tegra->domain);
|
||||
if (!tegra->domain) {
|
||||
err = -ENOMEM;
|
||||
goto free;
|
||||
}
|
||||
|
||||
|
@ -47,11 +136,23 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
|
|||
|
||||
mutex_init(&tegra->clients_lock);
|
||||
INIT_LIST_HEAD(&tegra->clients);
|
||||
|
||||
mutex_init(&tegra->commit.lock);
|
||||
INIT_WORK(&tegra->commit.work, tegra_atomic_work);
|
||||
|
||||
drm->dev_private = tegra;
|
||||
tegra->drm = drm;
|
||||
|
||||
drm_mode_config_init(drm);
|
||||
|
||||
drm->mode_config.min_width = 0;
|
||||
drm->mode_config.min_height = 0;
|
||||
|
||||
drm->mode_config.max_width = 4096;
|
||||
drm->mode_config.max_height = 4096;
|
||||
|
||||
drm->mode_config.funcs = &tegra_drm_mode_funcs;
|
||||
|
||||
err = tegra_drm_fb_prepare(drm);
|
||||
if (err < 0)
|
||||
goto config;
|
||||
|
@ -62,6 +163,8 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
|
|||
if (err < 0)
|
||||
goto fbdev;
|
||||
|
||||
drm_mode_config_reset(drm);
|
||||
|
||||
/*
|
||||
* We don't use the drm_irq_install() helpers provided by the DRM
|
||||
* core, so we need to set this manually in order to allow the
|
||||
|
@ -106,8 +209,8 @@ static int tegra_drm_unload(struct drm_device *drm)
|
|||
|
||||
drm_kms_helper_poll_fini(drm);
|
||||
tegra_drm_fb_exit(drm);
|
||||
drm_vblank_cleanup(drm);
|
||||
drm_mode_config_cleanup(drm);
|
||||
drm_vblank_cleanup(drm);
|
||||
|
||||
err = host1x_device_exit(device);
|
||||
if (err < 0)
|
||||
|
@ -190,7 +293,7 @@ static int host1x_reloc_copy_from_user(struct host1x_reloc *dest,
|
|||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = get_user(dest->target.offset, &src->cmdbuf.offset);
|
||||
err = get_user(dest->target.offset, &src->target.offset);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
|
@ -893,6 +996,30 @@ static int host1x_drm_remove(struct host1x_device *dev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int host1x_drm_suspend(struct device *dev)
|
||||
{
|
||||
struct drm_device *drm = dev_get_drvdata(dev);
|
||||
|
||||
drm_kms_helper_poll_disable(drm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int host1x_drm_resume(struct device *dev)
|
||||
{
|
||||
struct drm_device *drm = dev_get_drvdata(dev);
|
||||
|
||||
drm_kms_helper_poll_enable(drm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops host1x_drm_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(host1x_drm_suspend, host1x_drm_resume)
|
||||
};
|
||||
|
||||
static const struct of_device_id host1x_drm_subdevs[] = {
|
||||
{ .compatible = "nvidia,tegra20-dc", },
|
||||
{ .compatible = "nvidia,tegra20-hdmi", },
|
||||
|
@ -912,7 +1039,10 @@ static const struct of_device_id host1x_drm_subdevs[] = {
|
|||
};
|
||||
|
||||
static struct host1x_driver host1x_drm_driver = {
|
||||
.name = "drm",
|
||||
.driver = {
|
||||
.name = "drm",
|
||||
.pm = &host1x_drm_pm_ops,
|
||||
},
|
||||
.probe = host1x_drm_probe,
|
||||
.remove = host1x_drm_remove,
|
||||
.subdevs = host1x_drm_subdevs,
|
||||
|
|
|
@ -50,6 +50,12 @@ struct tegra_drm {
|
|||
#endif
|
||||
|
||||
unsigned int pitch_align;
|
||||
|
||||
struct {
|
||||
struct drm_atomic_state *state;
|
||||
struct work_struct work;
|
||||
struct mutex lock;
|
||||
} commit;
|
||||
};
|
||||
|
||||
struct tegra_drm_client;
|
||||
|
@ -164,45 +170,31 @@ struct tegra_dc_window {
|
|||
unsigned int h;
|
||||
} dst;
|
||||
unsigned int bits_per_pixel;
|
||||
unsigned int format;
|
||||
unsigned int swap;
|
||||
unsigned int stride[2];
|
||||
unsigned long base[3];
|
||||
bool bottom_up;
|
||||
|
||||
struct tegra_bo_tiling tiling;
|
||||
u32 format;
|
||||
u32 swap;
|
||||
};
|
||||
|
||||
/* from dc.c */
|
||||
void tegra_dc_enable_vblank(struct tegra_dc *dc);
|
||||
void tegra_dc_disable_vblank(struct tegra_dc *dc);
|
||||
void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file);
|
||||
|
||||
struct tegra_output_ops {
|
||||
int (*enable)(struct tegra_output *output);
|
||||
int (*disable)(struct tegra_output *output);
|
||||
int (*setup_clock)(struct tegra_output *output, struct clk *clk,
|
||||
unsigned long pclk, unsigned int *div);
|
||||
int (*check_mode)(struct tegra_output *output,
|
||||
struct drm_display_mode *mode,
|
||||
enum drm_mode_status *status);
|
||||
enum drm_connector_status (*detect)(struct tegra_output *output);
|
||||
};
|
||||
|
||||
enum tegra_output_type {
|
||||
TEGRA_OUTPUT_RGB,
|
||||
TEGRA_OUTPUT_HDMI,
|
||||
TEGRA_OUTPUT_DSI,
|
||||
TEGRA_OUTPUT_EDP,
|
||||
};
|
||||
void tegra_dc_commit(struct tegra_dc *dc);
|
||||
int tegra_dc_setup_clock(struct tegra_dc *dc, struct clk *parent,
|
||||
unsigned long pclk, unsigned int div);
|
||||
int tegra_dc_state_setup_clock(struct tegra_dc *dc,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct clk *clk, unsigned long pclk,
|
||||
unsigned int div);
|
||||
|
||||
struct tegra_output {
|
||||
struct device_node *of_node;
|
||||
struct device *dev;
|
||||
|
||||
const struct tegra_output_ops *ops;
|
||||
enum tegra_output_type type;
|
||||
|
||||
struct drm_panel *panel;
|
||||
struct i2c_adapter *ddc;
|
||||
const struct edid *edid;
|
||||
|
@ -223,42 +215,6 @@ static inline struct tegra_output *connector_to_output(struct drm_connector *c)
|
|||
return container_of(c, struct tegra_output, connector);
|
||||
}
|
||||
|
||||
static inline int tegra_output_enable(struct tegra_output *output)
|
||||
{
|
||||
if (output && output->ops && output->ops->enable)
|
||||
return output->ops->enable(output);
|
||||
|
||||
return output ? -ENOSYS : -EINVAL;
|
||||
}
|
||||
|
||||
static inline int tegra_output_disable(struct tegra_output *output)
|
||||
{
|
||||
if (output && output->ops && output->ops->disable)
|
||||
return output->ops->disable(output);
|
||||
|
||||
return output ? -ENOSYS : -EINVAL;
|
||||
}
|
||||
|
||||
static inline int tegra_output_setup_clock(struct tegra_output *output,
|
||||
struct clk *clk, unsigned long pclk,
|
||||
unsigned int *div)
|
||||
{
|
||||
if (output && output->ops && output->ops->setup_clock)
|
||||
return output->ops->setup_clock(output, clk, pclk, div);
|
||||
|
||||
return output ? -ENOSYS : -EINVAL;
|
||||
}
|
||||
|
||||
static inline int tegra_output_check_mode(struct tegra_output *output,
|
||||
struct drm_display_mode *mode,
|
||||
enum drm_mode_status *status)
|
||||
{
|
||||
if (output && output->ops && output->ops->check_mode)
|
||||
return output->ops->check_mode(output, mode, status);
|
||||
|
||||
return output ? -ENOSYS : -EINVAL;
|
||||
}
|
||||
|
||||
/* from rgb.c */
|
||||
int tegra_dc_rgb_probe(struct tegra_dc *dc);
|
||||
int tegra_dc_rgb_remove(struct tegra_dc *dc);
|
||||
|
@ -267,9 +223,18 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc);
|
|||
|
||||
/* from output.c */
|
||||
int tegra_output_probe(struct tegra_output *output);
|
||||
int tegra_output_remove(struct tegra_output *output);
|
||||
void tegra_output_remove(struct tegra_output *output);
|
||||
int tegra_output_init(struct drm_device *drm, struct tegra_output *output);
|
||||
int tegra_output_exit(struct tegra_output *output);
|
||||
void tegra_output_exit(struct tegra_output *output);
|
||||
|
||||
int tegra_output_connector_get_modes(struct drm_connector *connector);
|
||||
struct drm_encoder *
|
||||
tegra_output_connector_best_encoder(struct drm_connector *connector);
|
||||
enum drm_connector_status
|
||||
tegra_output_connector_detect(struct drm_connector *connector, bool force);
|
||||
void tegra_output_connector_destroy(struct drm_connector *connector);
|
||||
|
||||
void tegra_output_encoder_destroy(struct drm_encoder *encoder);
|
||||
|
||||
/* from dpaux.c */
|
||||
struct tegra_dpaux;
|
||||
|
@ -291,12 +256,16 @@ struct tegra_bo *tegra_fb_get_plane(struct drm_framebuffer *framebuffer,
|
|||
bool tegra_fb_is_bottom_up(struct drm_framebuffer *framebuffer);
|
||||
int tegra_fb_get_tiling(struct drm_framebuffer *framebuffer,
|
||||
struct tegra_bo_tiling *tiling);
|
||||
struct drm_framebuffer *tegra_fb_create(struct drm_device *drm,
|
||||
struct drm_file *file,
|
||||
struct drm_mode_fb_cmd2 *cmd);
|
||||
int tegra_drm_fb_prepare(struct drm_device *drm);
|
||||
void tegra_drm_fb_free(struct drm_device *drm);
|
||||
int tegra_drm_fb_init(struct drm_device *drm);
|
||||
void tegra_drm_fb_exit(struct drm_device *drm);
|
||||
#ifdef CONFIG_DRM_TEGRA_FBDEV
|
||||
void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev);
|
||||
void tegra_fb_output_poll_changed(struct drm_device *drm);
|
||||
#endif
|
||||
|
||||
extern struct platform_driver tegra_dc_driver;
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_mipi_dsi.h>
|
||||
#include <drm/drm_panel.h>
|
||||
|
||||
|
@ -27,6 +28,28 @@
|
|||
#include "dsi.h"
|
||||
#include "mipi-phy.h"
|
||||
|
||||
struct tegra_dsi_state {
|
||||
struct drm_connector_state base;
|
||||
|
||||
struct mipi_dphy_timing timing;
|
||||
unsigned long period;
|
||||
|
||||
unsigned int vrefresh;
|
||||
unsigned int lanes;
|
||||
unsigned long pclk;
|
||||
unsigned long bclk;
|
||||
|
||||
enum tegra_dsi_format format;
|
||||
unsigned int mul;
|
||||
unsigned int div;
|
||||
};
|
||||
|
||||
static inline struct tegra_dsi_state *
|
||||
to_dsi_state(struct drm_connector_state *state)
|
||||
{
|
||||
return container_of(state, struct tegra_dsi_state, base);
|
||||
}
|
||||
|
||||
struct tegra_dsi {
|
||||
struct host1x_client client;
|
||||
struct tegra_output output;
|
||||
|
@ -51,7 +74,6 @@ struct tegra_dsi {
|
|||
struct mipi_dsi_host host;
|
||||
|
||||
struct regulator *vdd;
|
||||
bool enabled;
|
||||
|
||||
unsigned int video_fifo_depth;
|
||||
unsigned int host_fifo_depth;
|
||||
|
@ -77,13 +99,17 @@ static inline struct tegra_dsi *to_dsi(struct tegra_output *output)
|
|||
return container_of(output, struct tegra_dsi, output);
|
||||
}
|
||||
|
||||
static inline unsigned long tegra_dsi_readl(struct tegra_dsi *dsi,
|
||||
unsigned long reg)
|
||||
static struct tegra_dsi_state *tegra_dsi_get_state(struct tegra_dsi *dsi)
|
||||
{
|
||||
return to_dsi_state(dsi->output.connector.state);
|
||||
}
|
||||
|
||||
static inline u32 tegra_dsi_readl(struct tegra_dsi *dsi, unsigned long reg)
|
||||
{
|
||||
return readl(dsi->regs + (reg << 2));
|
||||
}
|
||||
|
||||
static inline void tegra_dsi_writel(struct tegra_dsi *dsi, unsigned long value,
|
||||
static inline void tegra_dsi_writel(struct tegra_dsi *dsi, u32 value,
|
||||
unsigned long reg)
|
||||
{
|
||||
writel(value, dsi->regs + (reg << 2));
|
||||
|
@ -95,7 +121,7 @@ static int tegra_dsi_show_regs(struct seq_file *s, void *data)
|
|||
struct tegra_dsi *dsi = node->info_ent->data;
|
||||
|
||||
#define DUMP_REG(name) \
|
||||
seq_printf(s, "%-32s %#05x %08lx\n", #name, name, \
|
||||
seq_printf(s, "%-32s %#05x %08x\n", #name, name, \
|
||||
tegra_dsi_readl(dsi, name))
|
||||
|
||||
DUMP_REG(DSI_INCR_SYNCPT);
|
||||
|
@ -230,7 +256,7 @@ remove:
|
|||
return err;
|
||||
}
|
||||
|
||||
static int tegra_dsi_debugfs_exit(struct tegra_dsi *dsi)
|
||||
static void tegra_dsi_debugfs_exit(struct tegra_dsi *dsi)
|
||||
{
|
||||
drm_debugfs_remove_files(dsi->debugfs_files, ARRAY_SIZE(debugfs_files),
|
||||
dsi->minor);
|
||||
|
@ -241,8 +267,6 @@ static int tegra_dsi_debugfs_exit(struct tegra_dsi *dsi)
|
|||
|
||||
debugfs_remove(dsi->debugfs);
|
||||
dsi->debugfs = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define PKT_ID0(id) ((((id) & 0x3f) << 3) | (1 << 9))
|
||||
|
@ -338,61 +362,36 @@ static const u32 pkt_seq_command_mode[NUM_PKT_SEQ] = {
|
|||
[11] = 0,
|
||||
};
|
||||
|
||||
static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi)
|
||||
static void tegra_dsi_set_phy_timing(struct tegra_dsi *dsi,
|
||||
unsigned long period,
|
||||
const struct mipi_dphy_timing *timing)
|
||||
{
|
||||
struct mipi_dphy_timing timing;
|
||||
unsigned long value, period;
|
||||
long rate;
|
||||
int err;
|
||||
u32 value;
|
||||
|
||||
rate = clk_get_rate(dsi->clk);
|
||||
if (rate < 0)
|
||||
return rate;
|
||||
|
||||
period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, rate * 2);
|
||||
|
||||
err = mipi_dphy_timing_get_default(&timing, period);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = mipi_dphy_timing_validate(&timing, period);
|
||||
if (err < 0) {
|
||||
dev_err(dsi->dev, "failed to validate D-PHY timing: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* The D-PHY timing fields below are expressed in byte-clock cycles,
|
||||
* so multiply the period by 8.
|
||||
*/
|
||||
period *= 8;
|
||||
|
||||
value = DSI_TIMING_FIELD(timing.hsexit, period, 1) << 24 |
|
||||
DSI_TIMING_FIELD(timing.hstrail, period, 0) << 16 |
|
||||
DSI_TIMING_FIELD(timing.hszero, period, 3) << 8 |
|
||||
DSI_TIMING_FIELD(timing.hsprepare, period, 1);
|
||||
value = DSI_TIMING_FIELD(timing->hsexit, period, 1) << 24 |
|
||||
DSI_TIMING_FIELD(timing->hstrail, period, 0) << 16 |
|
||||
DSI_TIMING_FIELD(timing->hszero, period, 3) << 8 |
|
||||
DSI_TIMING_FIELD(timing->hsprepare, period, 1);
|
||||
tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_0);
|
||||
|
||||
value = DSI_TIMING_FIELD(timing.clktrail, period, 1) << 24 |
|
||||
DSI_TIMING_FIELD(timing.clkpost, period, 1) << 16 |
|
||||
DSI_TIMING_FIELD(timing.clkzero, period, 1) << 8 |
|
||||
DSI_TIMING_FIELD(timing.lpx, period, 1);
|
||||
value = DSI_TIMING_FIELD(timing->clktrail, period, 1) << 24 |
|
||||
DSI_TIMING_FIELD(timing->clkpost, period, 1) << 16 |
|
||||
DSI_TIMING_FIELD(timing->clkzero, period, 1) << 8 |
|
||||
DSI_TIMING_FIELD(timing->lpx, period, 1);
|
||||
tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_1);
|
||||
|
||||
value = DSI_TIMING_FIELD(timing.clkprepare, period, 1) << 16 |
|
||||
DSI_TIMING_FIELD(timing.clkpre, period, 1) << 8 |
|
||||
value = DSI_TIMING_FIELD(timing->clkprepare, period, 1) << 16 |
|
||||
DSI_TIMING_FIELD(timing->clkpre, period, 1) << 8 |
|
||||
DSI_TIMING_FIELD(0xff * period, period, 0) << 0;
|
||||
tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_2);
|
||||
|
||||
value = DSI_TIMING_FIELD(timing.taget, period, 1) << 16 |
|
||||
DSI_TIMING_FIELD(timing.tasure, period, 1) << 8 |
|
||||
DSI_TIMING_FIELD(timing.tago, period, 1);
|
||||
value = DSI_TIMING_FIELD(timing->taget, period, 1) << 16 |
|
||||
DSI_TIMING_FIELD(timing->tasure, period, 1) << 8 |
|
||||
DSI_TIMING_FIELD(timing->tago, period, 1);
|
||||
tegra_dsi_writel(dsi, value, DSI_BTA_TIMING);
|
||||
|
||||
if (dsi->slave)
|
||||
return tegra_dsi_set_phy_timing(dsi->slave);
|
||||
|
||||
return 0;
|
||||
tegra_dsi_set_phy_timing(dsi->slave, period, timing);
|
||||
}
|
||||
|
||||
static int tegra_dsi_get_muldiv(enum mipi_dsi_pixel_format format,
|
||||
|
@ -484,14 +483,22 @@ static unsigned int tegra_dsi_get_lanes(struct tegra_dsi *dsi)
|
|||
return dsi->lanes;
|
||||
}
|
||||
|
||||
static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
|
||||
const struct drm_display_mode *mode)
|
||||
static void tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
unsigned int hact, hsw, hbp, hfp, i, mul, div;
|
||||
enum tegra_dsi_format format;
|
||||
struct tegra_dsi_state *state;
|
||||
const u32 *pkt_seq;
|
||||
u32 value;
|
||||
int err;
|
||||
|
||||
/* XXX: pass in state into this function? */
|
||||
if (dsi->master)
|
||||
state = tegra_dsi_get_state(dsi->master);
|
||||
else
|
||||
state = tegra_dsi_get_state(dsi);
|
||||
|
||||
mul = state->mul;
|
||||
div = state->div;
|
||||
|
||||
if (dsi->flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
|
||||
DRM_DEBUG_KMS("Non-burst video mode with sync pulses\n");
|
||||
|
@ -504,15 +511,8 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
|
|||
pkt_seq = pkt_seq_command_mode;
|
||||
}
|
||||
|
||||
err = tegra_dsi_get_muldiv(dsi->format, &mul, &div);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = tegra_dsi_get_format(dsi->format, &format);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
value = DSI_CONTROL_CHANNEL(0) | DSI_CONTROL_FORMAT(format) |
|
||||
value = DSI_CONTROL_CHANNEL(0) |
|
||||
DSI_CONTROL_FORMAT(state->format) |
|
||||
DSI_CONTROL_LANES(dsi->lanes - 1) |
|
||||
DSI_CONTROL_SOURCE(pipe);
|
||||
tegra_dsi_writel(dsi, value, DSI_CONTROL);
|
||||
|
@ -591,8 +591,8 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
|
|||
|
||||
/* set SOL delay */
|
||||
if (dsi->master || dsi->slave) {
|
||||
unsigned int lanes = tegra_dsi_get_lanes(dsi);
|
||||
unsigned long delay, bclk, bclk_ganged;
|
||||
unsigned int lanes = state->lanes;
|
||||
|
||||
/* SOL to valid, valid to FIFO and FIFO write delay */
|
||||
delay = 4 + 4 + 2;
|
||||
|
@ -612,9 +612,7 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
|
|||
}
|
||||
|
||||
if (dsi->slave) {
|
||||
err = tegra_dsi_configure(dsi->slave, pipe, mode);
|
||||
if (err < 0)
|
||||
return err;
|
||||
tegra_dsi_configure(dsi->slave, pipe, mode);
|
||||
|
||||
/*
|
||||
* TODO: Support modes other than symmetrical left-right
|
||||
|
@ -624,49 +622,6 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
|
|||
tegra_dsi_ganged_enable(dsi->slave, mode->hdisplay / 2,
|
||||
mode->hdisplay / 2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_output_dsi_enable(struct tegra_output *output)
|
||||
{
|
||||
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
|
||||
const struct drm_display_mode *mode = &dc->base.mode;
|
||||
struct tegra_dsi *dsi = to_dsi(output);
|
||||
u32 value;
|
||||
int err;
|
||||
|
||||
if (dsi->enabled)
|
||||
return 0;
|
||||
|
||||
err = tegra_dsi_configure(dsi, dc->pipe, mode);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* enable display controller */
|
||||
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
|
||||
value |= DSI_ENABLE;
|
||||
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
|
||||
|
||||
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
|
||||
value &= ~DISP_CTRL_MODE_MASK;
|
||||
value |= DISP_CTRL_MODE_C_DISPLAY;
|
||||
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
|
||||
|
||||
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL);
|
||||
value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
|
||||
PW4_ENABLE | PM0_ENABLE | PM1_ENABLE;
|
||||
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
|
||||
|
||||
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
|
||||
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
|
||||
|
||||
/* enable DSI controller */
|
||||
tegra_dsi_enable(dsi);
|
||||
|
||||
dsi->enabled = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_dsi_wait_idle(struct tegra_dsi *dsi, unsigned long timeout)
|
||||
|
@ -705,70 +660,6 @@ static void tegra_dsi_ganged_disable(struct tegra_dsi *dsi)
|
|||
tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_CONTROL);
|
||||
}
|
||||
|
||||
static void tegra_dsi_disable(struct tegra_dsi *dsi)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
if (dsi->slave) {
|
||||
tegra_dsi_ganged_disable(dsi->slave);
|
||||
tegra_dsi_ganged_disable(dsi);
|
||||
}
|
||||
|
||||
value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
|
||||
value &= ~DSI_POWER_CONTROL_ENABLE;
|
||||
tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
|
||||
|
||||
if (dsi->slave)
|
||||
tegra_dsi_disable(dsi->slave);
|
||||
|
||||
usleep_range(5000, 10000);
|
||||
}
|
||||
|
||||
static int tegra_output_dsi_disable(struct tegra_output *output)
|
||||
{
|
||||
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
|
||||
struct tegra_dsi *dsi = to_dsi(output);
|
||||
unsigned long value;
|
||||
int err;
|
||||
|
||||
if (!dsi->enabled)
|
||||
return 0;
|
||||
|
||||
tegra_dsi_video_disable(dsi);
|
||||
|
||||
/*
|
||||
* The following accesses registers of the display controller, so make
|
||||
* sure it's only executed when the output is attached to one.
|
||||
*/
|
||||
if (dc) {
|
||||
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL);
|
||||
value &= ~(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
|
||||
PW4_ENABLE | PM0_ENABLE | PM1_ENABLE);
|
||||
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
|
||||
|
||||
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
|
||||
value &= ~DISP_CTRL_MODE_MASK;
|
||||
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
|
||||
|
||||
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
|
||||
value &= ~DSI_ENABLE;
|
||||
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
|
||||
|
||||
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
|
||||
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
|
||||
}
|
||||
|
||||
err = tegra_dsi_wait_idle(dsi, 100);
|
||||
if (err < 0)
|
||||
dev_dbg(dsi->dev, "failed to idle DSI: %d\n", err);
|
||||
|
||||
tegra_dsi_disable(dsi);
|
||||
|
||||
dsi->enabled = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk,
|
||||
unsigned int vrefresh)
|
||||
{
|
||||
|
@ -792,34 +683,248 @@ static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk,
|
|||
tegra_dsi_set_timeout(dsi->slave, bclk, vrefresh);
|
||||
}
|
||||
|
||||
static int tegra_output_dsi_setup_clock(struct tegra_output *output,
|
||||
struct clk *clk, unsigned long pclk,
|
||||
unsigned int *divp)
|
||||
static void tegra_dsi_disable(struct tegra_dsi *dsi)
|
||||
{
|
||||
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
|
||||
struct drm_display_mode *mode = &dc->base.mode;
|
||||
u32 value;
|
||||
|
||||
if (dsi->slave) {
|
||||
tegra_dsi_ganged_disable(dsi->slave);
|
||||
tegra_dsi_ganged_disable(dsi);
|
||||
}
|
||||
|
||||
value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
|
||||
value &= ~DSI_POWER_CONTROL_ENABLE;
|
||||
tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
|
||||
|
||||
if (dsi->slave)
|
||||
tegra_dsi_disable(dsi->slave);
|
||||
|
||||
usleep_range(5000, 10000);
|
||||
}
|
||||
|
||||
static void tegra_dsi_soft_reset(struct tegra_dsi *dsi)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
|
||||
value &= ~DSI_POWER_CONTROL_ENABLE;
|
||||
tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
|
||||
|
||||
usleep_range(300, 1000);
|
||||
|
||||
value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
|
||||
value |= DSI_POWER_CONTROL_ENABLE;
|
||||
tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
|
||||
|
||||
usleep_range(300, 1000);
|
||||
|
||||
value = tegra_dsi_readl(dsi, DSI_TRIGGER);
|
||||
if (value)
|
||||
tegra_dsi_writel(dsi, 0, DSI_TRIGGER);
|
||||
|
||||
if (dsi->slave)
|
||||
tegra_dsi_soft_reset(dsi->slave);
|
||||
}
|
||||
|
||||
static void tegra_dsi_connector_dpms(struct drm_connector *connector, int mode)
|
||||
{
|
||||
}
|
||||
|
||||
static void tegra_dsi_connector_reset(struct drm_connector *connector)
|
||||
{
|
||||
struct tegra_dsi_state *state;
|
||||
|
||||
kfree(connector->state);
|
||||
connector->state = NULL;
|
||||
|
||||
state = kzalloc(sizeof(*state), GFP_KERNEL);
|
||||
if (state)
|
||||
connector->state = &state->base;
|
||||
}
|
||||
|
||||
static struct drm_connector_state *
|
||||
tegra_dsi_connector_duplicate_state(struct drm_connector *connector)
|
||||
{
|
||||
struct tegra_dsi_state *state = to_dsi_state(connector->state);
|
||||
struct tegra_dsi_state *copy;
|
||||
|
||||
copy = kmemdup(state, sizeof(*state), GFP_KERNEL);
|
||||
if (!copy)
|
||||
return NULL;
|
||||
|
||||
return ©->base;
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs tegra_dsi_connector_funcs = {
|
||||
.dpms = tegra_dsi_connector_dpms,
|
||||
.reset = tegra_dsi_connector_reset,
|
||||
.detect = tegra_output_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = tegra_output_connector_destroy,
|
||||
.atomic_duplicate_state = tegra_dsi_connector_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static enum drm_mode_status
|
||||
tegra_dsi_connector_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
static const struct drm_connector_helper_funcs tegra_dsi_connector_helper_funcs = {
|
||||
.get_modes = tegra_output_connector_get_modes,
|
||||
.mode_valid = tegra_dsi_connector_mode_valid,
|
||||
.best_encoder = tegra_output_connector_best_encoder,
|
||||
};
|
||||
|
||||
static const struct drm_encoder_funcs tegra_dsi_encoder_funcs = {
|
||||
.destroy = tegra_output_encoder_destroy,
|
||||
};
|
||||
|
||||
static void tegra_dsi_encoder_dpms(struct drm_encoder *encoder, int mode)
|
||||
{
|
||||
}
|
||||
|
||||
static void tegra_dsi_encoder_prepare(struct drm_encoder *encoder)
|
||||
{
|
||||
}
|
||||
|
||||
static void tegra_dsi_encoder_commit(struct drm_encoder *encoder)
|
||||
{
|
||||
}
|
||||
|
||||
static void tegra_dsi_encoder_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted)
|
||||
{
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
|
||||
struct tegra_dsi *dsi = to_dsi(output);
|
||||
unsigned int mul, div, vrefresh, lanes;
|
||||
unsigned long bclk, plld;
|
||||
struct tegra_dsi_state *state;
|
||||
u32 value;
|
||||
|
||||
state = tegra_dsi_get_state(dsi);
|
||||
|
||||
tegra_dsi_set_timeout(dsi, state->bclk, state->vrefresh);
|
||||
|
||||
/*
|
||||
* The D-PHY timing fields are expressed in byte-clock cycles, so
|
||||
* multiply the period by 8.
|
||||
*/
|
||||
tegra_dsi_set_phy_timing(dsi, state->period * 8, &state->timing);
|
||||
|
||||
if (output->panel)
|
||||
drm_panel_prepare(output->panel);
|
||||
|
||||
tegra_dsi_configure(dsi, dc->pipe, mode);
|
||||
|
||||
/* enable display controller */
|
||||
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
|
||||
value |= DSI_ENABLE;
|
||||
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
|
||||
|
||||
tegra_dc_commit(dc);
|
||||
|
||||
/* enable DSI controller */
|
||||
tegra_dsi_enable(dsi);
|
||||
|
||||
if (output->panel)
|
||||
drm_panel_enable(output->panel);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void tegra_dsi_encoder_disable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
|
||||
struct tegra_dsi *dsi = to_dsi(output);
|
||||
u32 value;
|
||||
int err;
|
||||
|
||||
lanes = tegra_dsi_get_lanes(dsi);
|
||||
if (output->panel)
|
||||
drm_panel_disable(output->panel);
|
||||
|
||||
err = tegra_dsi_get_muldiv(dsi->format, &mul, &div);
|
||||
tegra_dsi_video_disable(dsi);
|
||||
|
||||
/*
|
||||
* The following accesses registers of the display controller, so make
|
||||
* sure it's only executed when the output is attached to one.
|
||||
*/
|
||||
if (dc) {
|
||||
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
|
||||
value &= ~DSI_ENABLE;
|
||||
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
|
||||
|
||||
tegra_dc_commit(dc);
|
||||
}
|
||||
|
||||
err = tegra_dsi_wait_idle(dsi, 100);
|
||||
if (err < 0)
|
||||
dev_dbg(dsi->dev, "failed to idle DSI: %d\n", err);
|
||||
|
||||
tegra_dsi_soft_reset(dsi);
|
||||
|
||||
if (output->panel)
|
||||
drm_panel_unprepare(output->panel);
|
||||
|
||||
tegra_dsi_disable(dsi);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int
|
||||
tegra_dsi_encoder_atomic_check(struct drm_encoder *encoder,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
struct tegra_dsi_state *state = to_dsi_state(conn_state);
|
||||
struct tegra_dc *dc = to_tegra_dc(conn_state->crtc);
|
||||
struct tegra_dsi *dsi = to_dsi(output);
|
||||
unsigned int scdiv;
|
||||
unsigned long plld;
|
||||
int err;
|
||||
|
||||
state->pclk = crtc_state->mode.clock * 1000;
|
||||
|
||||
err = tegra_dsi_get_muldiv(dsi->format, &state->mul, &state->div);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
DRM_DEBUG_KMS("mul: %u, div: %u, lanes: %u\n", mul, div, lanes);
|
||||
vrefresh = drm_mode_vrefresh(mode);
|
||||
DRM_DEBUG_KMS("vrefresh: %u\n", vrefresh);
|
||||
state->lanes = tegra_dsi_get_lanes(dsi);
|
||||
|
||||
err = tegra_dsi_get_format(dsi->format, &state->format);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
state->vrefresh = drm_mode_vrefresh(&crtc_state->mode);
|
||||
|
||||
/* compute byte clock */
|
||||
bclk = (pclk * mul) / (div * lanes);
|
||||
state->bclk = (state->pclk * state->mul) / (state->div * state->lanes);
|
||||
|
||||
DRM_DEBUG_KMS("mul: %u, div: %u, lanes: %u\n", state->mul, state->div,
|
||||
state->lanes);
|
||||
DRM_DEBUG_KMS("format: %u, vrefresh: %u\n", state->format,
|
||||
state->vrefresh);
|
||||
DRM_DEBUG_KMS("bclk: %lu\n", state->bclk);
|
||||
|
||||
/*
|
||||
* Compute bit clock and round up to the next MHz.
|
||||
*/
|
||||
plld = DIV_ROUND_UP(bclk * 8, USEC_PER_SEC) * USEC_PER_SEC;
|
||||
plld = DIV_ROUND_UP(state->bclk * 8, USEC_PER_SEC) * USEC_PER_SEC;
|
||||
state->period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, plld);
|
||||
|
||||
err = mipi_dphy_timing_get_default(&state->timing, state->period);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = mipi_dphy_timing_validate(&state->timing, state->period);
|
||||
if (err < 0) {
|
||||
dev_err(dsi->dev, "failed to validate D-PHY timing: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* We divide the frequency by two here, but we make up for that by
|
||||
|
@ -828,19 +933,6 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output,
|
|||
*/
|
||||
plld /= 2;
|
||||
|
||||
err = clk_set_parent(clk, dsi->clk_parent);
|
||||
if (err < 0) {
|
||||
dev_err(dsi->dev, "failed to set parent clock: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = clk_set_rate(dsi->clk_parent, plld);
|
||||
if (err < 0) {
|
||||
dev_err(dsi->dev, "failed to set base clock rate to %lu Hz\n",
|
||||
plld);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Derive pixel clock from bit clock using the shift clock divider.
|
||||
* Note that this is only half of what we would expect, but we need
|
||||
|
@ -851,44 +943,30 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output,
|
|||
* not working properly otherwise. Perhaps the PLLs cannot generate
|
||||
* frequencies sufficiently high.
|
||||
*/
|
||||
*divp = ((8 * mul) / (div * lanes)) - 2;
|
||||
scdiv = ((8 * state->mul) / (state->div * state->lanes)) - 2;
|
||||
|
||||
/*
|
||||
* XXX: Move the below somewhere else so that we don't need to have
|
||||
* access to the vrefresh in this function?
|
||||
*/
|
||||
tegra_dsi_set_timeout(dsi, bclk, vrefresh);
|
||||
|
||||
err = tegra_dsi_set_phy_timing(dsi);
|
||||
if (err < 0)
|
||||
err = tegra_dc_state_setup_clock(dc, crtc_state, dsi->clk_parent,
|
||||
plld, scdiv);
|
||||
if (err < 0) {
|
||||
dev_err(output->dev, "failed to setup CRTC state: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tegra_output_dsi_check_mode(struct tegra_output *output,
|
||||
struct drm_display_mode *mode,
|
||||
enum drm_mode_status *status)
|
||||
{
|
||||
/*
|
||||
* FIXME: For now, always assume that the mode is okay.
|
||||
*/
|
||||
|
||||
*status = MODE_OK;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct tegra_output_ops dsi_ops = {
|
||||
.enable = tegra_output_dsi_enable,
|
||||
.disable = tegra_output_dsi_disable,
|
||||
.setup_clock = tegra_output_dsi_setup_clock,
|
||||
.check_mode = tegra_output_dsi_check_mode,
|
||||
static const struct drm_encoder_helper_funcs tegra_dsi_encoder_helper_funcs = {
|
||||
.dpms = tegra_dsi_encoder_dpms,
|
||||
.prepare = tegra_dsi_encoder_prepare,
|
||||
.commit = tegra_dsi_encoder_commit,
|
||||
.mode_set = tegra_dsi_encoder_mode_set,
|
||||
.disable = tegra_dsi_encoder_disable,
|
||||
.atomic_check = tegra_dsi_encoder_atomic_check,
|
||||
};
|
||||
|
||||
static int tegra_dsi_pad_enable(struct tegra_dsi *dsi)
|
||||
{
|
||||
unsigned long value;
|
||||
u32 value;
|
||||
|
||||
value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0);
|
||||
tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0);
|
||||
|
@ -923,17 +1001,44 @@ static int tegra_dsi_init(struct host1x_client *client)
|
|||
struct tegra_dsi *dsi = host1x_client_to_dsi(client);
|
||||
int err;
|
||||
|
||||
reset_control_deassert(dsi->rst);
|
||||
|
||||
err = tegra_dsi_pad_calibrate(dsi);
|
||||
if (err < 0) {
|
||||
dev_err(dsi->dev, "MIPI calibration failed: %d\n", err);
|
||||
goto reset;
|
||||
}
|
||||
|
||||
/* Gangsters must not register their own outputs. */
|
||||
if (!dsi->master) {
|
||||
dsi->output.type = TEGRA_OUTPUT_DSI;
|
||||
dsi->output.dev = client->dev;
|
||||
dsi->output.ops = &dsi_ops;
|
||||
|
||||
drm_connector_init(drm, &dsi->output.connector,
|
||||
&tegra_dsi_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_DSI);
|
||||
drm_connector_helper_add(&dsi->output.connector,
|
||||
&tegra_dsi_connector_helper_funcs);
|
||||
dsi->output.connector.dpms = DRM_MODE_DPMS_OFF;
|
||||
|
||||
drm_encoder_init(drm, &dsi->output.encoder,
|
||||
&tegra_dsi_encoder_funcs,
|
||||
DRM_MODE_ENCODER_DSI);
|
||||
drm_encoder_helper_add(&dsi->output.encoder,
|
||||
&tegra_dsi_encoder_helper_funcs);
|
||||
|
||||
drm_mode_connector_attach_encoder(&dsi->output.connector,
|
||||
&dsi->output.encoder);
|
||||
drm_connector_register(&dsi->output.connector);
|
||||
|
||||
err = tegra_output_init(drm, &dsi->output);
|
||||
if (err < 0) {
|
||||
dev_err(client->dev, "output setup failed: %d\n", err);
|
||||
return err;
|
||||
dev_err(client->dev,
|
||||
"failed to initialize output: %d\n",
|
||||
err);
|
||||
goto reset;
|
||||
}
|
||||
|
||||
dsi->output.encoder.possible_crtcs = 0x3;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_DEBUG_FS)) {
|
||||
|
@ -943,34 +1048,22 @@ static int tegra_dsi_init(struct host1x_client *client)
|
|||
}
|
||||
|
||||
return 0;
|
||||
|
||||
reset:
|
||||
reset_control_assert(dsi->rst);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tegra_dsi_exit(struct host1x_client *client)
|
||||
{
|
||||
struct tegra_dsi *dsi = host1x_client_to_dsi(client);
|
||||
int err;
|
||||
|
||||
if (IS_ENABLED(CONFIG_DEBUG_FS)) {
|
||||
err = tegra_dsi_debugfs_exit(dsi);
|
||||
if (err < 0)
|
||||
dev_err(dsi->dev, "debugfs cleanup failed: %d\n", err);
|
||||
}
|
||||
tegra_output_exit(&dsi->output);
|
||||
|
||||
if (!dsi->master) {
|
||||
err = tegra_output_disable(&dsi->output);
|
||||
if (err < 0) {
|
||||
dev_err(client->dev, "output failed to disable: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
if (IS_ENABLED(CONFIG_DEBUG_FS))
|
||||
tegra_dsi_debugfs_exit(dsi);
|
||||
|
||||
err = tegra_output_exit(&dsi->output);
|
||||
if (err < 0) {
|
||||
dev_err(client->dev, "output cleanup failed: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
reset_control_assert(dsi->rst);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1398,13 +1491,6 @@ static int tegra_dsi_probe(struct platform_device *pdev)
|
|||
if (IS_ERR(dsi->rst))
|
||||
return PTR_ERR(dsi->rst);
|
||||
|
||||
err = reset_control_deassert(dsi->rst);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to bring DSI out of reset: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
dsi->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(dsi->clk)) {
|
||||
dev_err(&pdev->dev, "cannot get DSI clock\n");
|
||||
|
@ -1470,12 +1556,6 @@ static int tegra_dsi_probe(struct platform_device *pdev)
|
|||
goto disable_vdd;
|
||||
}
|
||||
|
||||
err = tegra_dsi_pad_calibrate(dsi);
|
||||
if (err < 0) {
|
||||
dev_err(dsi->dev, "MIPI calibration failed: %d\n", err);
|
||||
goto mipi_free;
|
||||
}
|
||||
|
||||
dsi->host.ops = &tegra_dsi_host_ops;
|
||||
dsi->host.dev = &pdev->dev;
|
||||
|
||||
|
@ -1527,6 +1607,8 @@ static int tegra_dsi_remove(struct platform_device *pdev)
|
|||
return err;
|
||||
}
|
||||
|
||||
tegra_output_remove(&dsi->output);
|
||||
|
||||
mipi_dsi_host_unregister(&dsi->host);
|
||||
tegra_mipi_free(dsi->mipi);
|
||||
|
||||
|
@ -1535,12 +1617,6 @@ static int tegra_dsi_remove(struct platform_device *pdev)
|
|||
clk_disable_unprepare(dsi->clk);
|
||||
reset_control_assert(dsi->rst);
|
||||
|
||||
err = tegra_output_remove(&dsi->output);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to remove output: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -129,9 +129,9 @@ static struct tegra_fb *tegra_fb_alloc(struct drm_device *drm,
|
|||
return fb;
|
||||
}
|
||||
|
||||
static struct drm_framebuffer *tegra_fb_create(struct drm_device *drm,
|
||||
struct drm_file *file,
|
||||
struct drm_mode_fb_cmd2 *cmd)
|
||||
struct drm_framebuffer *tegra_fb_create(struct drm_device *drm,
|
||||
struct drm_file *file,
|
||||
struct drm_mode_fb_cmd2 *cmd)
|
||||
{
|
||||
unsigned int hsub, vsub, i;
|
||||
struct tegra_bo *planes[4];
|
||||
|
@ -377,7 +377,7 @@ void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev)
|
|||
drm_fb_helper_restore_fbdev_mode_unlocked(&fbdev->base);
|
||||
}
|
||||
|
||||
static void tegra_fb_output_poll_changed(struct drm_device *drm)
|
||||
void tegra_fb_output_poll_changed(struct drm_device *drm)
|
||||
{
|
||||
struct tegra_drm *tegra = drm->dev_private;
|
||||
|
||||
|
@ -386,28 +386,11 @@ static void tegra_fb_output_poll_changed(struct drm_device *drm)
|
|||
}
|
||||
#endif
|
||||
|
||||
static const struct drm_mode_config_funcs tegra_drm_mode_funcs = {
|
||||
.fb_create = tegra_fb_create,
|
||||
#ifdef CONFIG_DRM_TEGRA_FBDEV
|
||||
.output_poll_changed = tegra_fb_output_poll_changed,
|
||||
#endif
|
||||
};
|
||||
|
||||
int tegra_drm_fb_prepare(struct drm_device *drm)
|
||||
{
|
||||
#ifdef CONFIG_DRM_TEGRA_FBDEV
|
||||
struct tegra_drm *tegra = drm->dev_private;
|
||||
#endif
|
||||
|
||||
drm->mode_config.min_width = 0;
|
||||
drm->mode_config.min_height = 0;
|
||||
|
||||
drm->mode_config.max_width = 4096;
|
||||
drm->mode_config.max_height = 4096;
|
||||
|
||||
drm->mode_config.funcs = &tegra_drm_mode_funcs;
|
||||
|
||||
#ifdef CONFIG_DRM_TEGRA_FBDEV
|
||||
tegra->fbdev = tegra_fbdev_create(drm);
|
||||
if (IS_ERR(tegra->fbdev))
|
||||
return PTR_ERR(tegra->fbdev);
|
||||
|
|
|
@ -92,36 +92,6 @@ static const struct host1x_bo_ops tegra_bo_ops = {
|
|||
.kunmap = tegra_bo_kunmap,
|
||||
};
|
||||
|
||||
/*
|
||||
* A generic iommu_map_sg() function is being reviewed and will hopefully be
|
||||
* merged soon. At that point this function can be dropped in favour of the
|
||||
* one provided by the IOMMU API.
|
||||
*/
|
||||
static ssize_t __iommu_map_sg(struct iommu_domain *domain, unsigned long iova,
|
||||
struct scatterlist *sg, unsigned int nents,
|
||||
int prot)
|
||||
{
|
||||
struct scatterlist *s;
|
||||
size_t offset = 0;
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
for_each_sg(sg, s, nents, i) {
|
||||
phys_addr_t phys = page_to_phys(sg_page(s));
|
||||
size_t length = s->offset + s->length;
|
||||
|
||||
err = iommu_map(domain, iova + offset, phys, length, prot);
|
||||
if (err < 0) {
|
||||
iommu_unmap(domain, iova, offset);
|
||||
return err;
|
||||
}
|
||||
|
||||
offset += length;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static int tegra_bo_iommu_map(struct tegra_drm *tegra, struct tegra_bo *bo)
|
||||
{
|
||||
int prot = IOMMU_READ | IOMMU_WRITE;
|
||||
|
@ -144,8 +114,8 @@ static int tegra_bo_iommu_map(struct tegra_drm *tegra, struct tegra_bo *bo)
|
|||
|
||||
bo->paddr = bo->mm->start;
|
||||
|
||||
err = __iommu_map_sg(tegra->domain, bo->paddr, bo->sgt->sgl,
|
||||
bo->sgt->nents, prot);
|
||||
err = iommu_map_sg(tegra->domain, bo->paddr, bo->sgt->sgl,
|
||||
bo->sgt->nents, prot);
|
||||
if (err < 0) {
|
||||
dev_err(tegra->drm->dev, "failed to map buffer: %zd\n", err);
|
||||
goto remove;
|
||||
|
@ -244,10 +214,8 @@ static int tegra_bo_get_pages(struct drm_device *drm, struct tegra_bo *bo)
|
|||
for_each_sg(sgt->sgl, s, sgt->nents, i)
|
||||
sg_dma_address(s) = sg_phys(s);
|
||||
|
||||
if (dma_map_sg(drm->dev, sgt->sgl, sgt->nents, DMA_TO_DEVICE) == 0) {
|
||||
sgt = ERR_PTR(-ENOMEM);
|
||||
if (dma_map_sg(drm->dev, sgt->sgl, sgt->nents, DMA_TO_DEVICE) == 0)
|
||||
goto release_sgt;
|
||||
}
|
||||
|
||||
bo->sgt = sgt;
|
||||
|
||||
|
@ -256,6 +224,7 @@ static int tegra_bo_get_pages(struct drm_device *drm, struct tegra_bo *bo)
|
|||
release_sgt:
|
||||
sg_free_table(sgt);
|
||||
kfree(sgt);
|
||||
sgt = ERR_PTR(-ENOMEM);
|
||||
put_pages:
|
||||
drm_gem_put_pages(&bo->gem, bo->pages, false, false);
|
||||
return PTR_ERR(sgt);
|
||||
|
|
|
@ -9,10 +9,15 @@
|
|||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/hdmi.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
|
||||
#include "hdmi.h"
|
||||
#include "drm.h"
|
||||
#include "dc.h"
|
||||
|
@ -31,7 +36,7 @@ struct tegra_hdmi_config {
|
|||
unsigned int num_tmds;
|
||||
|
||||
unsigned long fuse_override_offset;
|
||||
unsigned long fuse_override_value;
|
||||
u32 fuse_override_value;
|
||||
|
||||
bool has_sor_io_peak_current;
|
||||
};
|
||||
|
@ -40,7 +45,6 @@ struct tegra_hdmi {
|
|||
struct host1x_client client;
|
||||
struct tegra_output output;
|
||||
struct device *dev;
|
||||
bool enabled;
|
||||
|
||||
struct regulator *hdmi;
|
||||
struct regulator *pll;
|
||||
|
@ -85,16 +89,16 @@ enum {
|
|||
HDA,
|
||||
};
|
||||
|
||||
static inline unsigned long tegra_hdmi_readl(struct tegra_hdmi *hdmi,
|
||||
unsigned long reg)
|
||||
static inline u32 tegra_hdmi_readl(struct tegra_hdmi *hdmi,
|
||||
unsigned long offset)
|
||||
{
|
||||
return readl(hdmi->regs + (reg << 2));
|
||||
return readl(hdmi->regs + (offset << 2));
|
||||
}
|
||||
|
||||
static inline void tegra_hdmi_writel(struct tegra_hdmi *hdmi, unsigned long val,
|
||||
unsigned long reg)
|
||||
static inline void tegra_hdmi_writel(struct tegra_hdmi *hdmi, u32 value,
|
||||
unsigned long offset)
|
||||
{
|
||||
writel(val, hdmi->regs + (reg << 2));
|
||||
writel(value, hdmi->regs + (offset << 2));
|
||||
}
|
||||
|
||||
struct tegra_hdmi_audio_config {
|
||||
|
@ -455,8 +459,8 @@ static void tegra_hdmi_setup_audio_fs_tables(struct tegra_hdmi *hdmi)
|
|||
for (i = 0; i < ARRAY_SIZE(freqs); i++) {
|
||||
unsigned int f = freqs[i];
|
||||
unsigned int eight_half;
|
||||
unsigned long value;
|
||||
unsigned int delta;
|
||||
u32 value;
|
||||
|
||||
if (f > 96000)
|
||||
delta = 2;
|
||||
|
@ -477,7 +481,7 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi, unsigned int pclk)
|
|||
struct device_node *node = hdmi->dev->of_node;
|
||||
const struct tegra_hdmi_audio_config *config;
|
||||
unsigned int offset = 0;
|
||||
unsigned long value;
|
||||
u32 value;
|
||||
|
||||
switch (hdmi->audio_source) {
|
||||
case HDA:
|
||||
|
@ -571,9 +575,9 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi, unsigned int pclk)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static inline unsigned long tegra_hdmi_subpack(const u8 *ptr, size_t size)
|
||||
static inline u32 tegra_hdmi_subpack(const u8 *ptr, size_t size)
|
||||
{
|
||||
unsigned long value = 0;
|
||||
u32 value = 0;
|
||||
size_t i;
|
||||
|
||||
for (i = size; i > 0; i--)
|
||||
|
@ -587,8 +591,8 @@ static void tegra_hdmi_write_infopack(struct tegra_hdmi *hdmi, const void *data,
|
|||
{
|
||||
const u8 *ptr = data;
|
||||
unsigned long offset;
|
||||
unsigned long value;
|
||||
size_t i, j;
|
||||
u32 value;
|
||||
|
||||
switch (ptr[0]) {
|
||||
case HDMI_INFOFRAME_TYPE_AVI:
|
||||
|
@ -707,9 +711,9 @@ static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi)
|
|||
static void tegra_hdmi_setup_stereo_infoframe(struct tegra_hdmi *hdmi)
|
||||
{
|
||||
struct hdmi_vendor_infoframe frame;
|
||||
unsigned long value;
|
||||
u8 buffer[10];
|
||||
ssize_t err;
|
||||
u32 value;
|
||||
|
||||
if (!hdmi->stereo) {
|
||||
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL);
|
||||
|
@ -738,7 +742,7 @@ static void tegra_hdmi_setup_stereo_infoframe(struct tegra_hdmi *hdmi)
|
|||
static void tegra_hdmi_setup_tmds(struct tegra_hdmi *hdmi,
|
||||
const struct tmds_config *tmds)
|
||||
{
|
||||
unsigned long value;
|
||||
u32 value;
|
||||
|
||||
tegra_hdmi_writel(hdmi, tmds->pll0, HDMI_NV_PDISP_SOR_PLL0);
|
||||
tegra_hdmi_writel(hdmi, tmds->pll1, HDMI_NV_PDISP_SOR_PLL1);
|
||||
|
@ -768,21 +772,78 @@ static bool tegra_output_is_hdmi(struct tegra_output *output)
|
|||
return drm_detect_hdmi_monitor(edid);
|
||||
}
|
||||
|
||||
static int tegra_output_hdmi_enable(struct tegra_output *output)
|
||||
static void tegra_hdmi_connector_dpms(struct drm_connector *connector,
|
||||
int mode)
|
||||
{
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs tegra_hdmi_connector_funcs = {
|
||||
.dpms = tegra_hdmi_connector_dpms,
|
||||
.reset = drm_atomic_helper_connector_reset,
|
||||
.detect = tegra_output_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = tegra_output_connector_destroy,
|
||||
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static enum drm_mode_status
|
||||
tegra_hdmi_connector_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
struct tegra_output *output = connector_to_output(connector);
|
||||
struct tegra_hdmi *hdmi = to_hdmi(output);
|
||||
unsigned long pclk = mode->clock * 1000;
|
||||
enum drm_mode_status status = MODE_OK;
|
||||
struct clk *parent;
|
||||
long err;
|
||||
|
||||
parent = clk_get_parent(hdmi->clk_parent);
|
||||
|
||||
err = clk_round_rate(parent, pclk * 4);
|
||||
if (err <= 0)
|
||||
status = MODE_NOCLOCK;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static const struct drm_connector_helper_funcs
|
||||
tegra_hdmi_connector_helper_funcs = {
|
||||
.get_modes = tegra_output_connector_get_modes,
|
||||
.mode_valid = tegra_hdmi_connector_mode_valid,
|
||||
.best_encoder = tegra_output_connector_best_encoder,
|
||||
};
|
||||
|
||||
static const struct drm_encoder_funcs tegra_hdmi_encoder_funcs = {
|
||||
.destroy = tegra_output_encoder_destroy,
|
||||
};
|
||||
|
||||
static void tegra_hdmi_encoder_dpms(struct drm_encoder *encoder, int mode)
|
||||
{
|
||||
}
|
||||
|
||||
static void tegra_hdmi_encoder_prepare(struct drm_encoder *encoder)
|
||||
{
|
||||
}
|
||||
|
||||
static void tegra_hdmi_encoder_commit(struct drm_encoder *encoder)
|
||||
{
|
||||
}
|
||||
|
||||
static void tegra_hdmi_encoder_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted)
|
||||
{
|
||||
unsigned int h_sync_width, h_front_porch, h_back_porch, i, rekey;
|
||||
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
|
||||
struct drm_display_mode *mode = &dc->base.mode;
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
|
||||
struct device_node *node = output->dev->of_node;
|
||||
struct tegra_hdmi *hdmi = to_hdmi(output);
|
||||
struct device_node *node = hdmi->dev->of_node;
|
||||
unsigned int pulse_start, div82, pclk;
|
||||
unsigned long value;
|
||||
int retries = 1000;
|
||||
u32 value;
|
||||
int err;
|
||||
|
||||
if (hdmi->enabled)
|
||||
return 0;
|
||||
|
||||
hdmi->dvi = !tegra_output_is_hdmi(output);
|
||||
|
||||
pclk = mode->clock * 1000;
|
||||
|
@ -790,32 +851,6 @@ static int tegra_output_hdmi_enable(struct tegra_output *output)
|
|||
h_back_porch = mode->htotal - mode->hsync_end;
|
||||
h_front_porch = mode->hsync_start - mode->hdisplay;
|
||||
|
||||
err = regulator_enable(hdmi->pll);
|
||||
if (err < 0) {
|
||||
dev_err(hdmi->dev, "failed to enable PLL regulator: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = regulator_enable(hdmi->vdd);
|
||||
if (err < 0) {
|
||||
dev_err(hdmi->dev, "failed to enable VDD regulator: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = clk_set_rate(hdmi->clk, pclk);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = clk_prepare_enable(hdmi->clk);
|
||||
if (err < 0) {
|
||||
dev_err(hdmi->dev, "failed to enable clock: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
reset_control_assert(hdmi->rst);
|
||||
usleep_range(1000, 2000);
|
||||
reset_control_deassert(hdmi->rst);
|
||||
|
||||
/* power up sequence */
|
||||
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_PLL0);
|
||||
value &= ~SOR_PLL_PDBG;
|
||||
|
@ -987,123 +1022,57 @@ static int tegra_output_hdmi_enable(struct tegra_output *output)
|
|||
value |= HDMI_ENABLE;
|
||||
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
|
||||
|
||||
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
|
||||
value &= ~DISP_CTRL_MODE_MASK;
|
||||
value |= DISP_CTRL_MODE_C_DISPLAY;
|
||||
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
|
||||
|
||||
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL);
|
||||
value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
|
||||
PW4_ENABLE | PM0_ENABLE | PM1_ENABLE;
|
||||
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
|
||||
|
||||
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
|
||||
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
|
||||
tegra_dc_commit(dc);
|
||||
|
||||
/* TODO: add HDCP support */
|
||||
|
||||
hdmi->enabled = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_output_hdmi_disable(struct tegra_output *output)
|
||||
static void tegra_hdmi_encoder_disable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
|
||||
struct tegra_hdmi *hdmi = to_hdmi(output);
|
||||
unsigned long value;
|
||||
|
||||
if (!hdmi->enabled)
|
||||
return 0;
|
||||
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
|
||||
u32 value;
|
||||
|
||||
/*
|
||||
* The following accesses registers of the display controller, so make
|
||||
* sure it's only executed when the output is attached to one.
|
||||
*/
|
||||
if (dc) {
|
||||
/*
|
||||
* XXX: We can't do this here because it causes HDMI to go
|
||||
* into an erroneous state with the result that HDMI won't
|
||||
* properly work once disabled. See also a similar symptom
|
||||
* for the SOR output.
|
||||
*/
|
||||
/*
|
||||
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL);
|
||||
value &= ~(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
|
||||
PW4_ENABLE | PM0_ENABLE | PM1_ENABLE);
|
||||
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
|
||||
*/
|
||||
|
||||
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
|
||||
value &= ~DISP_CTRL_MODE_MASK;
|
||||
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
|
||||
|
||||
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
|
||||
value &= ~HDMI_ENABLE;
|
||||
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
|
||||
|
||||
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
|
||||
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
|
||||
tegra_dc_commit(dc);
|
||||
}
|
||||
|
||||
clk_disable_unprepare(hdmi->clk);
|
||||
reset_control_assert(hdmi->rst);
|
||||
regulator_disable(hdmi->vdd);
|
||||
regulator_disable(hdmi->pll);
|
||||
|
||||
hdmi->enabled = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_output_hdmi_setup_clock(struct tegra_output *output,
|
||||
struct clk *clk, unsigned long pclk,
|
||||
unsigned int *div)
|
||||
static int
|
||||
tegra_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
struct tegra_dc *dc = to_tegra_dc(conn_state->crtc);
|
||||
unsigned long pclk = crtc_state->mode.clock * 1000;
|
||||
struct tegra_hdmi *hdmi = to_hdmi(output);
|
||||
int err;
|
||||
|
||||
err = clk_set_parent(clk, hdmi->clk_parent);
|
||||
err = tegra_dc_state_setup_clock(dc, crtc_state, hdmi->clk_parent,
|
||||
pclk, 0);
|
||||
if (err < 0) {
|
||||
dev_err(output->dev, "failed to set parent: %d\n", err);
|
||||
dev_err(output->dev, "failed to setup CRTC state: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = clk_set_rate(hdmi->clk_parent, pclk);
|
||||
if (err < 0)
|
||||
dev_err(output->dev, "failed to set clock rate to %lu Hz\n",
|
||||
pclk);
|
||||
|
||||
*div = 0;
|
||||
|
||||
return 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tegra_output_hdmi_check_mode(struct tegra_output *output,
|
||||
struct drm_display_mode *mode,
|
||||
enum drm_mode_status *status)
|
||||
{
|
||||
struct tegra_hdmi *hdmi = to_hdmi(output);
|
||||
unsigned long pclk = mode->clock * 1000;
|
||||
struct clk *parent;
|
||||
long err;
|
||||
|
||||
parent = clk_get_parent(hdmi->clk_parent);
|
||||
|
||||
err = clk_round_rate(parent, pclk * 4);
|
||||
if (err <= 0)
|
||||
*status = MODE_NOCLOCK;
|
||||
else
|
||||
*status = MODE_OK;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct tegra_output_ops hdmi_ops = {
|
||||
.enable = tegra_output_hdmi_enable,
|
||||
.disable = tegra_output_hdmi_disable,
|
||||
.setup_clock = tegra_output_hdmi_setup_clock,
|
||||
.check_mode = tegra_output_hdmi_check_mode,
|
||||
static const struct drm_encoder_helper_funcs tegra_hdmi_encoder_helper_funcs = {
|
||||
.dpms = tegra_hdmi_encoder_dpms,
|
||||
.prepare = tegra_hdmi_encoder_prepare,
|
||||
.commit = tegra_hdmi_encoder_commit,
|
||||
.mode_set = tegra_hdmi_encoder_mode_set,
|
||||
.disable = tegra_hdmi_encoder_disable,
|
||||
.atomic_check = tegra_hdmi_encoder_atomic_check,
|
||||
};
|
||||
|
||||
static int tegra_hdmi_show_regs(struct seq_file *s, void *data)
|
||||
|
@ -1117,8 +1086,8 @@ static int tegra_hdmi_show_regs(struct seq_file *s, void *data)
|
|||
return err;
|
||||
|
||||
#define DUMP_REG(name) \
|
||||
seq_printf(s, "%-56s %#05x %08lx\n", #name, name, \
|
||||
tegra_hdmi_readl(hdmi, name))
|
||||
seq_printf(s, "%-56s %#05x %08x\n", #name, name, \
|
||||
tegra_hdmi_readl(hdmi, name))
|
||||
|
||||
DUMP_REG(HDMI_CTXSW);
|
||||
DUMP_REG(HDMI_NV_PDISP_SOR_STATE0);
|
||||
|
@ -1330,7 +1299,7 @@ remove:
|
|||
return err;
|
||||
}
|
||||
|
||||
static int tegra_hdmi_debugfs_exit(struct tegra_hdmi *hdmi)
|
||||
static void tegra_hdmi_debugfs_exit(struct tegra_hdmi *hdmi)
|
||||
{
|
||||
drm_debugfs_remove_files(hdmi->debugfs_files, ARRAY_SIZE(debugfs_files),
|
||||
hdmi->minor);
|
||||
|
@ -1341,8 +1310,6 @@ static int tegra_hdmi_debugfs_exit(struct tegra_hdmi *hdmi)
|
|||
|
||||
debugfs_remove(hdmi->debugfs);
|
||||
hdmi->debugfs = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_hdmi_init(struct host1x_client *client)
|
||||
|
@ -1351,16 +1318,32 @@ static int tegra_hdmi_init(struct host1x_client *client)
|
|||
struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client);
|
||||
int err;
|
||||
|
||||
hdmi->output.type = TEGRA_OUTPUT_HDMI;
|
||||
hdmi->output.dev = client->dev;
|
||||
hdmi->output.ops = &hdmi_ops;
|
||||
|
||||
drm_connector_init(drm, &hdmi->output.connector,
|
||||
&tegra_hdmi_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_HDMIA);
|
||||
drm_connector_helper_add(&hdmi->output.connector,
|
||||
&tegra_hdmi_connector_helper_funcs);
|
||||
hdmi->output.connector.dpms = DRM_MODE_DPMS_OFF;
|
||||
|
||||
drm_encoder_init(drm, &hdmi->output.encoder, &tegra_hdmi_encoder_funcs,
|
||||
DRM_MODE_ENCODER_TMDS);
|
||||
drm_encoder_helper_add(&hdmi->output.encoder,
|
||||
&tegra_hdmi_encoder_helper_funcs);
|
||||
|
||||
drm_mode_connector_attach_encoder(&hdmi->output.connector,
|
||||
&hdmi->output.encoder);
|
||||
drm_connector_register(&hdmi->output.connector);
|
||||
|
||||
err = tegra_output_init(drm, &hdmi->output);
|
||||
if (err < 0) {
|
||||
dev_err(client->dev, "output setup failed: %d\n", err);
|
||||
dev_err(client->dev, "failed to initialize output: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
hdmi->output.encoder.possible_crtcs = 0x3;
|
||||
|
||||
if (IS_ENABLED(CONFIG_DEBUG_FS)) {
|
||||
err = tegra_hdmi_debugfs_init(hdmi, drm->primary);
|
||||
if (err < 0)
|
||||
|
@ -1374,34 +1357,44 @@ static int tegra_hdmi_init(struct host1x_client *client)
|
|||
return err;
|
||||
}
|
||||
|
||||
err = regulator_enable(hdmi->pll);
|
||||
if (err < 0) {
|
||||
dev_err(hdmi->dev, "failed to enable PLL regulator: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = regulator_enable(hdmi->vdd);
|
||||
if (err < 0) {
|
||||
dev_err(hdmi->dev, "failed to enable VDD regulator: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = clk_prepare_enable(hdmi->clk);
|
||||
if (err < 0) {
|
||||
dev_err(hdmi->dev, "failed to enable clock: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
reset_control_deassert(hdmi->rst);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_hdmi_exit(struct host1x_client *client)
|
||||
{
|
||||
struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client);
|
||||
int err;
|
||||
|
||||
tegra_output_exit(&hdmi->output);
|
||||
|
||||
clk_disable_unprepare(hdmi->clk);
|
||||
reset_control_assert(hdmi->rst);
|
||||
|
||||
regulator_disable(hdmi->vdd);
|
||||
regulator_disable(hdmi->pll);
|
||||
regulator_disable(hdmi->hdmi);
|
||||
|
||||
if (IS_ENABLED(CONFIG_DEBUG_FS)) {
|
||||
err = tegra_hdmi_debugfs_exit(hdmi);
|
||||
if (err < 0)
|
||||
dev_err(client->dev, "debugfs cleanup failed: %d\n",
|
||||
err);
|
||||
}
|
||||
|
||||
err = tegra_output_disable(&hdmi->output);
|
||||
if (err < 0) {
|
||||
dev_err(client->dev, "output failed to disable: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = tegra_output_exit(&hdmi->output);
|
||||
if (err < 0) {
|
||||
dev_err(client->dev, "output cleanup failed: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
if (IS_ENABLED(CONFIG_DEBUG_FS))
|
||||
tegra_hdmi_debugfs_exit(hdmi);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1559,11 +1552,7 @@ static int tegra_hdmi_remove(struct platform_device *pdev)
|
|||
return err;
|
||||
}
|
||||
|
||||
err = tegra_output_remove(&hdmi->output);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to remove output: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
tegra_output_remove(&hdmi->output);
|
||||
|
||||
clk_disable_unprepare(hdmi->clk_parent);
|
||||
clk_disable_unprepare(hdmi->clk);
|
||||
|
|
|
@ -12,9 +12,9 @@
|
|||
#include "mipi-phy.h"
|
||||
|
||||
/*
|
||||
* Default D-PHY timings based on MIPI D-PHY specification. Derived from
|
||||
* the valid ranges specified in Section 5.9 of the D-PHY specification
|
||||
* with minor adjustments.
|
||||
* Default D-PHY timings based on MIPI D-PHY specification. Derived from the
|
||||
* valid ranges specified in Section 6.9, Table 14, Page 40 of the D-PHY
|
||||
* specification (v1.2) with minor adjustments.
|
||||
*/
|
||||
int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing,
|
||||
unsigned long period)
|
||||
|
@ -34,7 +34,20 @@ int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing,
|
|||
timing->hszero = 145 + 5 * period;
|
||||
timing->hssettle = 85 + 6 * period;
|
||||
timing->hsskip = 40;
|
||||
timing->hstrail = max(8 * period, 60 + 4 * period);
|
||||
|
||||
/*
|
||||
* The MIPI D-PHY specification (Section 6.9, v1.2, Table 14, Page 40)
|
||||
* contains this formula as:
|
||||
*
|
||||
* T_HS-TRAIL = max(n * 8 * period, 60 + n * 4 * period)
|
||||
*
|
||||
* where n = 1 for forward-direction HS mode and n = 4 for reverse-
|
||||
* direction HS mode. There's only one setting and this function does
|
||||
* not parameterize on anything other that period, so this code will
|
||||
* assumes that reverse-direction HS mode is supported and uses n = 4.
|
||||
*/
|
||||
timing->hstrail = max(4 * 8 * period, 60 + 4 * 4 * period);
|
||||
|
||||
timing->init = 100000;
|
||||
timing->lpx = 60;
|
||||
timing->taget = 5 * timing->lpx;
|
||||
|
@ -46,8 +59,8 @@ int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing,
|
|||
}
|
||||
|
||||
/*
|
||||
* Validate D-PHY timing according to MIPI Alliance Specification for D-PHY,
|
||||
* Section 5.9 "Global Operation Timing Parameters".
|
||||
* Validate D-PHY timing according to MIPI D-PHY specification (v1.2, Section
|
||||
* Section 6.9 "Global Operation Timing Parameters").
|
||||
*/
|
||||
int mipi_dphy_timing_validate(struct mipi_dphy_timing *timing,
|
||||
unsigned long period)
|
||||
|
|
|
@ -9,10 +9,11 @@
|
|||
|
||||
#include <linux/of_gpio.h>
|
||||
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include "drm.h"
|
||||
|
||||
static int tegra_connector_get_modes(struct drm_connector *connector)
|
||||
int tegra_output_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct tegra_output *output = connector_to_output(connector);
|
||||
struct edid *edid = NULL;
|
||||
|
@ -43,43 +44,20 @@ static int tegra_connector_get_modes(struct drm_connector *connector)
|
|||
return err;
|
||||
}
|
||||
|
||||
static int tegra_connector_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
struct tegra_output *output = connector_to_output(connector);
|
||||
enum drm_mode_status status = MODE_OK;
|
||||
int err;
|
||||
|
||||
err = tegra_output_check_mode(output, mode, &status);
|
||||
if (err < 0)
|
||||
return MODE_ERROR;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static struct drm_encoder *
|
||||
tegra_connector_best_encoder(struct drm_connector *connector)
|
||||
struct drm_encoder *
|
||||
tegra_output_connector_best_encoder(struct drm_connector *connector)
|
||||
{
|
||||
struct tegra_output *output = connector_to_output(connector);
|
||||
|
||||
return &output->encoder;
|
||||
}
|
||||
|
||||
static const struct drm_connector_helper_funcs connector_helper_funcs = {
|
||||
.get_modes = tegra_connector_get_modes,
|
||||
.mode_valid = tegra_connector_mode_valid,
|
||||
.best_encoder = tegra_connector_best_encoder,
|
||||
};
|
||||
|
||||
static enum drm_connector_status
|
||||
tegra_connector_detect(struct drm_connector *connector, bool force)
|
||||
enum drm_connector_status
|
||||
tegra_output_connector_detect(struct drm_connector *connector, bool force)
|
||||
{
|
||||
struct tegra_output *output = connector_to_output(connector);
|
||||
enum drm_connector_status status = connector_status_unknown;
|
||||
|
||||
if (output->ops->detect)
|
||||
return output->ops->detect(output);
|
||||
|
||||
if (gpio_is_valid(output->hpd_gpio)) {
|
||||
if (gpio_get_value(output->hpd_gpio) == 0)
|
||||
status = connector_status_disconnected;
|
||||
|
@ -90,95 +68,22 @@ tegra_connector_detect(struct drm_connector *connector, bool force)
|
|||
status = connector_status_disconnected;
|
||||
else
|
||||
status = connector_status_connected;
|
||||
|
||||
if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS)
|
||||
status = connector_status_connected;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void drm_connector_clear(struct drm_connector *connector)
|
||||
{
|
||||
memset(connector, 0, sizeof(*connector));
|
||||
}
|
||||
|
||||
static void tegra_connector_destroy(struct drm_connector *connector)
|
||||
void tegra_output_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
drm_connector_unregister(connector);
|
||||
drm_connector_cleanup(connector);
|
||||
drm_connector_clear(connector);
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs connector_funcs = {
|
||||
.dpms = drm_helper_connector_dpms,
|
||||
.detect = tegra_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = tegra_connector_destroy,
|
||||
};
|
||||
|
||||
static void drm_encoder_clear(struct drm_encoder *encoder)
|
||||
{
|
||||
memset(encoder, 0, sizeof(*encoder));
|
||||
}
|
||||
|
||||
static void tegra_encoder_destroy(struct drm_encoder *encoder)
|
||||
void tegra_output_encoder_destroy(struct drm_encoder *encoder)
|
||||
{
|
||||
drm_encoder_cleanup(encoder);
|
||||
drm_encoder_clear(encoder);
|
||||
}
|
||||
|
||||
static const struct drm_encoder_funcs encoder_funcs = {
|
||||
.destroy = tegra_encoder_destroy,
|
||||
};
|
||||
|
||||
static void tegra_encoder_dpms(struct drm_encoder *encoder, int mode)
|
||||
{
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
struct drm_panel *panel = output->panel;
|
||||
|
||||
if (mode != DRM_MODE_DPMS_ON) {
|
||||
drm_panel_disable(panel);
|
||||
tegra_output_disable(output);
|
||||
drm_panel_unprepare(panel);
|
||||
} else {
|
||||
drm_panel_prepare(panel);
|
||||
tegra_output_enable(output);
|
||||
drm_panel_enable(panel);
|
||||
}
|
||||
}
|
||||
|
||||
static bool tegra_encoder_mode_fixup(struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void tegra_encoder_prepare(struct drm_encoder *encoder)
|
||||
{
|
||||
tegra_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
|
||||
}
|
||||
|
||||
static void tegra_encoder_commit(struct drm_encoder *encoder)
|
||||
{
|
||||
tegra_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
|
||||
}
|
||||
|
||||
static void tegra_encoder_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted)
|
||||
{
|
||||
}
|
||||
|
||||
static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
|
||||
.dpms = tegra_encoder_dpms,
|
||||
.mode_fixup = tegra_encoder_mode_fixup,
|
||||
.prepare = tegra_encoder_prepare,
|
||||
.commit = tegra_encoder_commit,
|
||||
.mode_set = tegra_encoder_mode_set,
|
||||
};
|
||||
|
||||
static irqreturn_t hpd_irq(int irq, void *data)
|
||||
{
|
||||
struct tegra_output *output = data;
|
||||
|
@ -268,7 +173,7 @@ int tegra_output_probe(struct tegra_output *output)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int tegra_output_remove(struct tegra_output *output)
|
||||
void tegra_output_remove(struct tegra_output *output)
|
||||
{
|
||||
if (gpio_is_valid(output->hpd_gpio)) {
|
||||
free_irq(output->hpd_irq, output);
|
||||
|
@ -277,57 +182,18 @@ int tegra_output_remove(struct tegra_output *output)
|
|||
|
||||
if (output->ddc)
|
||||
put_device(&output->ddc->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tegra_output_init(struct drm_device *drm, struct tegra_output *output)
|
||||
{
|
||||
int connector, encoder;
|
||||
int err;
|
||||
|
||||
switch (output->type) {
|
||||
case TEGRA_OUTPUT_RGB:
|
||||
connector = DRM_MODE_CONNECTOR_LVDS;
|
||||
encoder = DRM_MODE_ENCODER_LVDS;
|
||||
break;
|
||||
|
||||
case TEGRA_OUTPUT_HDMI:
|
||||
connector = DRM_MODE_CONNECTOR_HDMIA;
|
||||
encoder = DRM_MODE_ENCODER_TMDS;
|
||||
break;
|
||||
|
||||
case TEGRA_OUTPUT_DSI:
|
||||
connector = DRM_MODE_CONNECTOR_DSI;
|
||||
encoder = DRM_MODE_ENCODER_DSI;
|
||||
break;
|
||||
|
||||
case TEGRA_OUTPUT_EDP:
|
||||
connector = DRM_MODE_CONNECTOR_eDP;
|
||||
encoder = DRM_MODE_ENCODER_TMDS;
|
||||
break;
|
||||
|
||||
default:
|
||||
connector = DRM_MODE_CONNECTOR_Unknown;
|
||||
encoder = DRM_MODE_ENCODER_NONE;
|
||||
break;
|
||||
if (output->panel) {
|
||||
err = drm_panel_attach(output->panel, &output->connector);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
drm_connector_init(drm, &output->connector, &connector_funcs,
|
||||
connector);
|
||||
drm_connector_helper_add(&output->connector, &connector_helper_funcs);
|
||||
output->connector.dpms = DRM_MODE_DPMS_OFF;
|
||||
|
||||
if (output->panel)
|
||||
drm_panel_attach(output->panel, &output->connector);
|
||||
|
||||
drm_encoder_init(drm, &output->encoder, &encoder_funcs, encoder);
|
||||
drm_encoder_helper_add(&output->encoder, &encoder_helper_funcs);
|
||||
|
||||
drm_mode_connector_attach_encoder(&output->connector, &output->encoder);
|
||||
drm_connector_register(&output->connector);
|
||||
|
||||
output->encoder.possible_crtcs = 0x3;
|
||||
|
||||
/*
|
||||
* The connector is now registered and ready to receive hotplug events
|
||||
* so the hotplug interrupt can be enabled.
|
||||
|
@ -338,7 +204,7 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int tegra_output_exit(struct tegra_output *output)
|
||||
void tegra_output_exit(struct tegra_output *output)
|
||||
{
|
||||
/*
|
||||
* The connector is going away, so the interrupt must be disabled to
|
||||
|
@ -349,6 +215,4 @@ int tegra_output_exit(struct tegra_output *output)
|
|||
|
||||
if (output->panel)
|
||||
drm_panel_detach(output->panel);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_panel.h>
|
||||
|
||||
#include "drm.h"
|
||||
#include "dc.h"
|
||||
|
||||
|
@ -85,13 +88,65 @@ static void tegra_dc_write_regs(struct tegra_dc *dc,
|
|||
tegra_dc_writel(dc, table[i].value, table[i].offset);
|
||||
}
|
||||
|
||||
static int tegra_output_rgb_enable(struct tegra_output *output)
|
||||
static void tegra_rgb_connector_dpms(struct drm_connector *connector,
|
||||
int mode)
|
||||
{
|
||||
struct tegra_rgb *rgb = to_rgb(output);
|
||||
unsigned long value;
|
||||
}
|
||||
|
||||
if (rgb->enabled)
|
||||
return 0;
|
||||
static const struct drm_connector_funcs tegra_rgb_connector_funcs = {
|
||||
.dpms = tegra_rgb_connector_dpms,
|
||||
.reset = drm_atomic_helper_connector_reset,
|
||||
.detect = tegra_output_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = tegra_output_connector_destroy,
|
||||
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static enum drm_mode_status
|
||||
tegra_rgb_connector_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
/*
|
||||
* FIXME: For now, always assume that the mode is okay. There are
|
||||
* unresolved issues with clk_round_rate(), which doesn't always
|
||||
* reliably report whether a frequency can be set or not.
|
||||
*/
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
static const struct drm_connector_helper_funcs tegra_rgb_connector_helper_funcs = {
|
||||
.get_modes = tegra_output_connector_get_modes,
|
||||
.mode_valid = tegra_rgb_connector_mode_valid,
|
||||
.best_encoder = tegra_output_connector_best_encoder,
|
||||
};
|
||||
|
||||
static const struct drm_encoder_funcs tegra_rgb_encoder_funcs = {
|
||||
.destroy = tegra_output_encoder_destroy,
|
||||
};
|
||||
|
||||
static void tegra_rgb_encoder_dpms(struct drm_encoder *encoder, int mode)
|
||||
{
|
||||
}
|
||||
|
||||
static void tegra_rgb_encoder_prepare(struct drm_encoder *encoder)
|
||||
{
|
||||
}
|
||||
|
||||
static void tegra_rgb_encoder_commit(struct drm_encoder *encoder)
|
||||
{
|
||||
}
|
||||
|
||||
static void tegra_rgb_encoder_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted)
|
||||
{
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
struct tegra_rgb *rgb = to_rgb(output);
|
||||
u32 value;
|
||||
|
||||
if (output->panel)
|
||||
drm_panel_prepare(output->panel);
|
||||
|
||||
tegra_dc_write_regs(rgb->dc, rgb_enable, ARRAY_SIZE(rgb_enable));
|
||||
|
||||
|
@ -113,64 +168,39 @@ static int tegra_output_rgb_enable(struct tegra_output *output)
|
|||
value = SC0_H_QUALIFIER_NONE | SC1_H_QUALIFIER_NONE;
|
||||
tegra_dc_writel(rgb->dc, value, DC_DISP_SHIFT_CLOCK_OPTIONS);
|
||||
|
||||
value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_COMMAND);
|
||||
value &= ~DISP_CTRL_MODE_MASK;
|
||||
value |= DISP_CTRL_MODE_C_DISPLAY;
|
||||
tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_COMMAND);
|
||||
tegra_dc_commit(rgb->dc);
|
||||
|
||||
value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_POWER_CONTROL);
|
||||
value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
|
||||
PW4_ENABLE | PM0_ENABLE | PM1_ENABLE;
|
||||
tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
|
||||
|
||||
tegra_dc_writel(rgb->dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
|
||||
tegra_dc_writel(rgb->dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
|
||||
|
||||
rgb->enabled = true;
|
||||
|
||||
return 0;
|
||||
if (output->panel)
|
||||
drm_panel_enable(output->panel);
|
||||
}
|
||||
|
||||
static int tegra_output_rgb_disable(struct tegra_output *output)
|
||||
static void tegra_rgb_encoder_disable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
struct tegra_rgb *rgb = to_rgb(output);
|
||||
unsigned long value;
|
||||
|
||||
if (!rgb->enabled)
|
||||
return 0;
|
||||
|
||||
value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_POWER_CONTROL);
|
||||
value &= ~(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
|
||||
PW4_ENABLE | PM0_ENABLE | PM1_ENABLE);
|
||||
tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
|
||||
|
||||
value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_COMMAND);
|
||||
value &= ~DISP_CTRL_MODE_MASK;
|
||||
tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_COMMAND);
|
||||
|
||||
tegra_dc_writel(rgb->dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
|
||||
tegra_dc_writel(rgb->dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
|
||||
if (output->panel)
|
||||
drm_panel_disable(output->panel);
|
||||
|
||||
tegra_dc_write_regs(rgb->dc, rgb_disable, ARRAY_SIZE(rgb_disable));
|
||||
tegra_dc_commit(rgb->dc);
|
||||
|
||||
rgb->enabled = false;
|
||||
|
||||
return 0;
|
||||
if (output->panel)
|
||||
drm_panel_unprepare(output->panel);
|
||||
}
|
||||
|
||||
static int tegra_output_rgb_setup_clock(struct tegra_output *output,
|
||||
struct clk *clk, unsigned long pclk,
|
||||
unsigned int *div)
|
||||
static int
|
||||
tegra_rgb_encoder_atomic_check(struct drm_encoder *encoder,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
struct tegra_dc *dc = to_tegra_dc(conn_state->crtc);
|
||||
unsigned long pclk = crtc_state->mode.clock * 1000;
|
||||
struct tegra_rgb *rgb = to_rgb(output);
|
||||
unsigned int div;
|
||||
int err;
|
||||
|
||||
err = clk_set_parent(clk, rgb->clk_parent);
|
||||
if (err < 0) {
|
||||
dev_err(output->dev, "failed to set parent: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* We may not want to change the frequency of the parent clock, since
|
||||
* it may be a parent for other peripherals. This is due to the fact
|
||||
|
@ -187,32 +217,26 @@ static int tegra_output_rgb_setup_clock(struct tegra_output *output,
|
|||
* and hope that the desired frequency can be matched (or at least
|
||||
* matched sufficiently close that the panel will still work).
|
||||
*/
|
||||
div = ((clk_get_rate(rgb->clk) * 2) / pclk) - 2;
|
||||
pclk = 0;
|
||||
|
||||
*div = ((clk_get_rate(clk) * 2) / pclk) - 2;
|
||||
err = tegra_dc_state_setup_clock(dc, crtc_state, rgb->clk_parent,
|
||||
pclk, div);
|
||||
if (err < 0) {
|
||||
dev_err(output->dev, "failed to setup CRTC state: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tegra_output_rgb_check_mode(struct tegra_output *output,
|
||||
struct drm_display_mode *mode,
|
||||
enum drm_mode_status *status)
|
||||
{
|
||||
/*
|
||||
* FIXME: For now, always assume that the mode is okay. There are
|
||||
* unresolved issues with clk_round_rate(), which doesn't always
|
||||
* reliably report whether a frequency can be set or not.
|
||||
*/
|
||||
|
||||
*status = MODE_OK;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct tegra_output_ops rgb_ops = {
|
||||
.enable = tegra_output_rgb_enable,
|
||||
.disable = tegra_output_rgb_disable,
|
||||
.setup_clock = tegra_output_rgb_setup_clock,
|
||||
.check_mode = tegra_output_rgb_check_mode,
|
||||
static const struct drm_encoder_helper_funcs tegra_rgb_encoder_helper_funcs = {
|
||||
.dpms = tegra_rgb_encoder_dpms,
|
||||
.prepare = tegra_rgb_encoder_prepare,
|
||||
.commit = tegra_rgb_encoder_commit,
|
||||
.mode_set = tegra_rgb_encoder_mode_set,
|
||||
.disable = tegra_rgb_encoder_disable,
|
||||
.atomic_check = tegra_rgb_encoder_atomic_check,
|
||||
};
|
||||
|
||||
int tegra_dc_rgb_probe(struct tegra_dc *dc)
|
||||
|
@ -262,64 +286,58 @@ int tegra_dc_rgb_probe(struct tegra_dc *dc)
|
|||
|
||||
int tegra_dc_rgb_remove(struct tegra_dc *dc)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (!dc->rgb)
|
||||
return 0;
|
||||
|
||||
err = tegra_output_remove(dc->rgb);
|
||||
if (err < 0)
|
||||
return err;
|
||||
tegra_output_remove(dc->rgb);
|
||||
dc->rgb = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc)
|
||||
{
|
||||
struct tegra_rgb *rgb = to_rgb(dc->rgb);
|
||||
struct tegra_output *output = dc->rgb;
|
||||
int err;
|
||||
|
||||
if (!dc->rgb)
|
||||
return -ENODEV;
|
||||
|
||||
rgb->output.type = TEGRA_OUTPUT_RGB;
|
||||
rgb->output.ops = &rgb_ops;
|
||||
drm_connector_init(drm, &output->connector, &tegra_rgb_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_LVDS);
|
||||
drm_connector_helper_add(&output->connector,
|
||||
&tegra_rgb_connector_helper_funcs);
|
||||
output->connector.dpms = DRM_MODE_DPMS_OFF;
|
||||
|
||||
err = tegra_output_init(dc->base.dev, &rgb->output);
|
||||
drm_encoder_init(drm, &output->encoder, &tegra_rgb_encoder_funcs,
|
||||
DRM_MODE_ENCODER_LVDS);
|
||||
drm_encoder_helper_add(&output->encoder,
|
||||
&tegra_rgb_encoder_helper_funcs);
|
||||
|
||||
drm_mode_connector_attach_encoder(&output->connector,
|
||||
&output->encoder);
|
||||
drm_connector_register(&output->connector);
|
||||
|
||||
err = tegra_output_init(drm, output);
|
||||
if (err < 0) {
|
||||
dev_err(dc->dev, "output setup failed: %d\n", err);
|
||||
dev_err(output->dev, "failed to initialize output: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* By default, outputs can be associated with each display controller.
|
||||
* RGB outputs are an exception, so we make sure they can be attached
|
||||
* to only their parent display controller.
|
||||
* Other outputs can be attached to either display controller. The RGB
|
||||
* outputs are an exception and work only with their parent display
|
||||
* controller.
|
||||
*/
|
||||
rgb->output.encoder.possible_crtcs = drm_crtc_mask(&dc->base);
|
||||
output->encoder.possible_crtcs = drm_crtc_mask(&dc->base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tegra_dc_rgb_exit(struct tegra_dc *dc)
|
||||
{
|
||||
if (dc->rgb) {
|
||||
int err;
|
||||
|
||||
err = tegra_output_disable(dc->rgb);
|
||||
if (err < 0) {
|
||||
dev_err(dc->dev, "output failed to disable: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = tegra_output_exit(dc->rgb);
|
||||
if (err < 0) {
|
||||
dev_err(dc->dev, "output cleanup failed: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
dc->rgb = NULL;
|
||||
}
|
||||
if (dc->rgb)
|
||||
tegra_output_exit(dc->rgb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -8,13 +8,16 @@
|
|||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include <soc/tegra/pmc.h>
|
||||
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_dp_helper.h>
|
||||
#include <drm/drm_panel.h>
|
||||
|
||||
#include "dc.h"
|
||||
#include "drm.h"
|
||||
|
@ -258,18 +261,8 @@ static int tegra_sor_attach(struct tegra_sor *sor)
|
|||
|
||||
static int tegra_sor_wakeup(struct tegra_sor *sor)
|
||||
{
|
||||
struct tegra_dc *dc = to_tegra_dc(sor->output.encoder.crtc);
|
||||
unsigned long value, timeout;
|
||||
|
||||
/* enable display controller outputs */
|
||||
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL);
|
||||
value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
|
||||
PW4_ENABLE | PM0_ENABLE | PM1_ENABLE;
|
||||
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
|
||||
|
||||
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
|
||||
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
|
||||
|
||||
timeout = jiffies + msecs_to_jiffies(250);
|
||||
|
||||
/* wait for head to wake up */
|
||||
|
@ -482,10 +475,317 @@ static int tegra_sor_calc_config(struct tegra_sor *sor,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_output_sor_enable(struct tegra_output *output)
|
||||
static int tegra_sor_detach(struct tegra_sor *sor)
|
||||
{
|
||||
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
|
||||
struct drm_display_mode *mode = &dc->base.mode;
|
||||
unsigned long value, timeout;
|
||||
|
||||
/* switch to safe mode */
|
||||
value = tegra_sor_readl(sor, SOR_SUPER_STATE_1);
|
||||
value &= ~SOR_SUPER_STATE_MODE_NORMAL;
|
||||
tegra_sor_writel(sor, value, SOR_SUPER_STATE_1);
|
||||
tegra_sor_super_update(sor);
|
||||
|
||||
timeout = jiffies + msecs_to_jiffies(250);
|
||||
|
||||
while (time_before(jiffies, timeout)) {
|
||||
value = tegra_sor_readl(sor, SOR_PWR);
|
||||
if (value & SOR_PWR_MODE_SAFE)
|
||||
break;
|
||||
}
|
||||
|
||||
if ((value & SOR_PWR_MODE_SAFE) == 0)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
/* go to sleep */
|
||||
value = tegra_sor_readl(sor, SOR_SUPER_STATE_1);
|
||||
value &= ~SOR_SUPER_STATE_HEAD_MODE_MASK;
|
||||
tegra_sor_writel(sor, value, SOR_SUPER_STATE_1);
|
||||
tegra_sor_super_update(sor);
|
||||
|
||||
/* detach */
|
||||
value = tegra_sor_readl(sor, SOR_SUPER_STATE_1);
|
||||
value &= ~SOR_SUPER_STATE_ATTACHED;
|
||||
tegra_sor_writel(sor, value, SOR_SUPER_STATE_1);
|
||||
tegra_sor_super_update(sor);
|
||||
|
||||
timeout = jiffies + msecs_to_jiffies(250);
|
||||
|
||||
while (time_before(jiffies, timeout)) {
|
||||
value = tegra_sor_readl(sor, SOR_TEST);
|
||||
if ((value & SOR_TEST_ATTACHED) == 0)
|
||||
break;
|
||||
|
||||
usleep_range(25, 100);
|
||||
}
|
||||
|
||||
if ((value & SOR_TEST_ATTACHED) != 0)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_sor_power_down(struct tegra_sor *sor)
|
||||
{
|
||||
unsigned long value, timeout;
|
||||
int err;
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_PWR);
|
||||
value &= ~SOR_PWR_NORMAL_STATE_PU;
|
||||
value |= SOR_PWR_TRIGGER;
|
||||
tegra_sor_writel(sor, value, SOR_PWR);
|
||||
|
||||
timeout = jiffies + msecs_to_jiffies(250);
|
||||
|
||||
while (time_before(jiffies, timeout)) {
|
||||
value = tegra_sor_readl(sor, SOR_PWR);
|
||||
if ((value & SOR_PWR_TRIGGER) == 0)
|
||||
return 0;
|
||||
|
||||
usleep_range(25, 100);
|
||||
}
|
||||
|
||||
if ((value & SOR_PWR_TRIGGER) != 0)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
err = clk_set_parent(sor->clk, sor->clk_safe);
|
||||
if (err < 0)
|
||||
dev_err(sor->dev, "failed to set safe parent clock: %d\n", err);
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_DP_PADCTL_0);
|
||||
value &= ~(SOR_DP_PADCTL_PD_TXD_3 | SOR_DP_PADCTL_PD_TXD_0 |
|
||||
SOR_DP_PADCTL_PD_TXD_1 | SOR_DP_PADCTL_PD_TXD_2);
|
||||
tegra_sor_writel(sor, value, SOR_DP_PADCTL_0);
|
||||
|
||||
/* stop lane sequencer */
|
||||
value = SOR_LANE_SEQ_CTL_TRIGGER | SOR_LANE_SEQ_CTL_SEQUENCE_UP |
|
||||
SOR_LANE_SEQ_CTL_POWER_STATE_DOWN;
|
||||
tegra_sor_writel(sor, value, SOR_LANE_SEQ_CTL);
|
||||
|
||||
timeout = jiffies + msecs_to_jiffies(250);
|
||||
|
||||
while (time_before(jiffies, timeout)) {
|
||||
value = tegra_sor_readl(sor, SOR_LANE_SEQ_CTL);
|
||||
if ((value & SOR_LANE_SEQ_CTL_TRIGGER) == 0)
|
||||
break;
|
||||
|
||||
usleep_range(25, 100);
|
||||
}
|
||||
|
||||
if ((value & SOR_LANE_SEQ_CTL_TRIGGER) != 0)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_PLL_2);
|
||||
value |= SOR_PLL_2_PORT_POWERDOWN;
|
||||
tegra_sor_writel(sor, value, SOR_PLL_2);
|
||||
|
||||
usleep_range(20, 100);
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_PLL_0);
|
||||
value |= SOR_PLL_0_POWER_OFF;
|
||||
value |= SOR_PLL_0_VCOPD;
|
||||
tegra_sor_writel(sor, value, SOR_PLL_0);
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_PLL_2);
|
||||
value |= SOR_PLL_2_SEQ_PLLCAPPD;
|
||||
value |= SOR_PLL_2_SEQ_PLLCAPPD_ENFORCE;
|
||||
tegra_sor_writel(sor, value, SOR_PLL_2);
|
||||
|
||||
usleep_range(20, 100);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_sor_crc_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
file->private_data = inode->i_private;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_sor_crc_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_sor_crc_wait(struct tegra_sor *sor, unsigned long timeout)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
timeout = jiffies + msecs_to_jiffies(timeout);
|
||||
|
||||
while (time_before(jiffies, timeout)) {
|
||||
value = tegra_sor_readl(sor, SOR_CRC_A);
|
||||
if (value & SOR_CRC_A_VALID)
|
||||
return 0;
|
||||
|
||||
usleep_range(100, 200);
|
||||
}
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static ssize_t tegra_sor_crc_read(struct file *file, char __user *buffer,
|
||||
size_t size, loff_t *ppos)
|
||||
{
|
||||
struct tegra_sor *sor = file->private_data;
|
||||
ssize_t num, err;
|
||||
char buf[10];
|
||||
u32 value;
|
||||
|
||||
mutex_lock(&sor->lock);
|
||||
|
||||
if (!sor->enabled) {
|
||||
err = -EAGAIN;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_STATE_1);
|
||||
value &= ~SOR_STATE_ASY_CRC_MODE_MASK;
|
||||
tegra_sor_writel(sor, value, SOR_STATE_1);
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_CRC_CNTRL);
|
||||
value |= SOR_CRC_CNTRL_ENABLE;
|
||||
tegra_sor_writel(sor, value, SOR_CRC_CNTRL);
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_TEST);
|
||||
value &= ~SOR_TEST_CRC_POST_SERIALIZE;
|
||||
tegra_sor_writel(sor, value, SOR_TEST);
|
||||
|
||||
err = tegra_sor_crc_wait(sor, 100);
|
||||
if (err < 0)
|
||||
goto unlock;
|
||||
|
||||
tegra_sor_writel(sor, SOR_CRC_A_RESET, SOR_CRC_A);
|
||||
value = tegra_sor_readl(sor, SOR_CRC_B);
|
||||
|
||||
num = scnprintf(buf, sizeof(buf), "%08x\n", value);
|
||||
|
||||
err = simple_read_from_buffer(buffer, size, ppos, buf, num);
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&sor->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct file_operations tegra_sor_crc_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = tegra_sor_crc_open,
|
||||
.read = tegra_sor_crc_read,
|
||||
.release = tegra_sor_crc_release,
|
||||
};
|
||||
|
||||
static int tegra_sor_debugfs_init(struct tegra_sor *sor,
|
||||
struct drm_minor *minor)
|
||||
{
|
||||
struct dentry *entry;
|
||||
int err = 0;
|
||||
|
||||
sor->debugfs = debugfs_create_dir("sor", minor->debugfs_root);
|
||||
if (!sor->debugfs)
|
||||
return -ENOMEM;
|
||||
|
||||
entry = debugfs_create_file("crc", 0644, sor->debugfs, sor,
|
||||
&tegra_sor_crc_fops);
|
||||
if (!entry) {
|
||||
dev_err(sor->dev,
|
||||
"cannot create /sys/kernel/debug/dri/%s/sor/crc\n",
|
||||
minor->debugfs_root->d_name.name);
|
||||
err = -ENOMEM;
|
||||
goto remove;
|
||||
}
|
||||
|
||||
return err;
|
||||
|
||||
remove:
|
||||
debugfs_remove(sor->debugfs);
|
||||
sor->debugfs = NULL;
|
||||
return err;
|
||||
}
|
||||
|
||||
static void tegra_sor_debugfs_exit(struct tegra_sor *sor)
|
||||
{
|
||||
debugfs_remove_recursive(sor->debugfs);
|
||||
sor->debugfs = NULL;
|
||||
}
|
||||
|
||||
static void tegra_sor_connector_dpms(struct drm_connector *connector, int mode)
|
||||
{
|
||||
}
|
||||
|
||||
static enum drm_connector_status
|
||||
tegra_sor_connector_detect(struct drm_connector *connector, bool force)
|
||||
{
|
||||
struct tegra_output *output = connector_to_output(connector);
|
||||
struct tegra_sor *sor = to_sor(output);
|
||||
|
||||
if (sor->dpaux)
|
||||
return tegra_dpaux_detect(sor->dpaux);
|
||||
|
||||
return connector_status_unknown;
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs tegra_sor_connector_funcs = {
|
||||
.dpms = tegra_sor_connector_dpms,
|
||||
.reset = drm_atomic_helper_connector_reset,
|
||||
.detect = tegra_sor_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = tegra_output_connector_destroy,
|
||||
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static int tegra_sor_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct tegra_output *output = connector_to_output(connector);
|
||||
struct tegra_sor *sor = to_sor(output);
|
||||
int err;
|
||||
|
||||
if (sor->dpaux)
|
||||
tegra_dpaux_enable(sor->dpaux);
|
||||
|
||||
err = tegra_output_connector_get_modes(connector);
|
||||
|
||||
if (sor->dpaux)
|
||||
tegra_dpaux_disable(sor->dpaux);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static enum drm_mode_status
|
||||
tegra_sor_connector_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
static const struct drm_connector_helper_funcs tegra_sor_connector_helper_funcs = {
|
||||
.get_modes = tegra_sor_connector_get_modes,
|
||||
.mode_valid = tegra_sor_connector_mode_valid,
|
||||
.best_encoder = tegra_output_connector_best_encoder,
|
||||
};
|
||||
|
||||
static const struct drm_encoder_funcs tegra_sor_encoder_funcs = {
|
||||
.destroy = tegra_output_encoder_destroy,
|
||||
};
|
||||
|
||||
static void tegra_sor_encoder_dpms(struct drm_encoder *encoder, int mode)
|
||||
{
|
||||
}
|
||||
|
||||
static void tegra_sor_encoder_prepare(struct drm_encoder *encoder)
|
||||
{
|
||||
}
|
||||
|
||||
static void tegra_sor_encoder_commit(struct drm_encoder *encoder)
|
||||
{
|
||||
}
|
||||
|
||||
static void tegra_sor_encoder_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted)
|
||||
{
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
|
||||
unsigned int vbe, vse, hbe, hse, vbs, hbs, i;
|
||||
struct tegra_sor *sor = to_sor(output);
|
||||
struct tegra_sor_config config;
|
||||
|
@ -505,6 +805,9 @@ static int tegra_output_sor_enable(struct tegra_output *output)
|
|||
|
||||
reset_control_deassert(sor->rst);
|
||||
|
||||
if (output->panel)
|
||||
drm_panel_prepare(output->panel);
|
||||
|
||||
/* FIXME: properly convert to struct drm_dp_aux */
|
||||
aux = (struct drm_dp_aux *)sor->dpaux;
|
||||
|
||||
|
@ -800,18 +1103,6 @@ static int tegra_output_sor_enable(struct tegra_output *output)
|
|||
goto unlock;
|
||||
}
|
||||
|
||||
/* start display controller in continuous mode */
|
||||
value = tegra_dc_readl(dc, DC_CMD_STATE_ACCESS);
|
||||
value |= WRITE_MUX;
|
||||
tegra_dc_writel(dc, value, DC_CMD_STATE_ACCESS);
|
||||
|
||||
tegra_dc_writel(dc, VSYNC_H_POSITION(1), DC_DISP_DISP_TIMING_OPTIONS);
|
||||
tegra_dc_writel(dc, DISP_CTRL_MODE_C_DISPLAY, DC_CMD_DISPLAY_COMMAND);
|
||||
|
||||
value = tegra_dc_readl(dc, DC_CMD_STATE_ACCESS);
|
||||
value &= ~WRITE_MUX;
|
||||
tegra_dc_writel(dc, value, DC_CMD_STATE_ACCESS);
|
||||
|
||||
/*
|
||||
* configure panel (24bpp, vsync-, hsync-, DP-A protocol, complete
|
||||
* raster, associate with display controller)
|
||||
|
@ -886,11 +1177,13 @@ static int tegra_output_sor_enable(struct tegra_output *output)
|
|||
goto unlock;
|
||||
}
|
||||
|
||||
tegra_sor_update(sor);
|
||||
|
||||
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
|
||||
value |= SOR_ENABLE;
|
||||
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
|
||||
|
||||
tegra_sor_update(sor);
|
||||
tegra_dc_commit(dc);
|
||||
|
||||
err = tegra_sor_attach(sor);
|
||||
if (err < 0) {
|
||||
|
@ -904,145 +1197,31 @@ static int tegra_output_sor_enable(struct tegra_output *output)
|
|||
goto unlock;
|
||||
}
|
||||
|
||||
if (output->panel)
|
||||
drm_panel_enable(output->panel);
|
||||
|
||||
sor->enabled = true;
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&sor->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tegra_sor_detach(struct tegra_sor *sor)
|
||||
static void tegra_sor_encoder_disable(struct drm_encoder *encoder)
|
||||
{
|
||||
unsigned long value, timeout;
|
||||
|
||||
/* switch to safe mode */
|
||||
value = tegra_sor_readl(sor, SOR_SUPER_STATE_1);
|
||||
value &= ~SOR_SUPER_STATE_MODE_NORMAL;
|
||||
tegra_sor_writel(sor, value, SOR_SUPER_STATE_1);
|
||||
tegra_sor_super_update(sor);
|
||||
|
||||
timeout = jiffies + msecs_to_jiffies(250);
|
||||
|
||||
while (time_before(jiffies, timeout)) {
|
||||
value = tegra_sor_readl(sor, SOR_PWR);
|
||||
if (value & SOR_PWR_MODE_SAFE)
|
||||
break;
|
||||
}
|
||||
|
||||
if ((value & SOR_PWR_MODE_SAFE) == 0)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
/* go to sleep */
|
||||
value = tegra_sor_readl(sor, SOR_SUPER_STATE_1);
|
||||
value &= ~SOR_SUPER_STATE_HEAD_MODE_MASK;
|
||||
tegra_sor_writel(sor, value, SOR_SUPER_STATE_1);
|
||||
tegra_sor_super_update(sor);
|
||||
|
||||
/* detach */
|
||||
value = tegra_sor_readl(sor, SOR_SUPER_STATE_1);
|
||||
value &= ~SOR_SUPER_STATE_ATTACHED;
|
||||
tegra_sor_writel(sor, value, SOR_SUPER_STATE_1);
|
||||
tegra_sor_super_update(sor);
|
||||
|
||||
timeout = jiffies + msecs_to_jiffies(250);
|
||||
|
||||
while (time_before(jiffies, timeout)) {
|
||||
value = tegra_sor_readl(sor, SOR_TEST);
|
||||
if ((value & SOR_TEST_ATTACHED) == 0)
|
||||
break;
|
||||
|
||||
usleep_range(25, 100);
|
||||
}
|
||||
|
||||
if ((value & SOR_TEST_ATTACHED) != 0)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_sor_power_down(struct tegra_sor *sor)
|
||||
{
|
||||
unsigned long value, timeout;
|
||||
int err;
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_PWR);
|
||||
value &= ~SOR_PWR_NORMAL_STATE_PU;
|
||||
value |= SOR_PWR_TRIGGER;
|
||||
tegra_sor_writel(sor, value, SOR_PWR);
|
||||
|
||||
timeout = jiffies + msecs_to_jiffies(250);
|
||||
|
||||
while (time_before(jiffies, timeout)) {
|
||||
value = tegra_sor_readl(sor, SOR_PWR);
|
||||
if ((value & SOR_PWR_TRIGGER) == 0)
|
||||
return 0;
|
||||
|
||||
usleep_range(25, 100);
|
||||
}
|
||||
|
||||
if ((value & SOR_PWR_TRIGGER) != 0)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
err = clk_set_parent(sor->clk, sor->clk_safe);
|
||||
if (err < 0)
|
||||
dev_err(sor->dev, "failed to set safe parent clock: %d\n", err);
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_DP_PADCTL_0);
|
||||
value &= ~(SOR_DP_PADCTL_PD_TXD_3 | SOR_DP_PADCTL_PD_TXD_0 |
|
||||
SOR_DP_PADCTL_PD_TXD_1 | SOR_DP_PADCTL_PD_TXD_2);
|
||||
tegra_sor_writel(sor, value, SOR_DP_PADCTL_0);
|
||||
|
||||
/* stop lane sequencer */
|
||||
value = SOR_LANE_SEQ_CTL_TRIGGER | SOR_LANE_SEQ_CTL_SEQUENCE_UP |
|
||||
SOR_LANE_SEQ_CTL_POWER_STATE_DOWN;
|
||||
tegra_sor_writel(sor, value, SOR_LANE_SEQ_CTL);
|
||||
|
||||
timeout = jiffies + msecs_to_jiffies(250);
|
||||
|
||||
while (time_before(jiffies, timeout)) {
|
||||
value = tegra_sor_readl(sor, SOR_LANE_SEQ_CTL);
|
||||
if ((value & SOR_LANE_SEQ_CTL_TRIGGER) == 0)
|
||||
break;
|
||||
|
||||
usleep_range(25, 100);
|
||||
}
|
||||
|
||||
if ((value & SOR_LANE_SEQ_CTL_TRIGGER) != 0)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_PLL_2);
|
||||
value |= SOR_PLL_2_PORT_POWERDOWN;
|
||||
tegra_sor_writel(sor, value, SOR_PLL_2);
|
||||
|
||||
usleep_range(20, 100);
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_PLL_0);
|
||||
value |= SOR_PLL_0_POWER_OFF;
|
||||
value |= SOR_PLL_0_VCOPD;
|
||||
tegra_sor_writel(sor, value, SOR_PLL_0);
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_PLL_2);
|
||||
value |= SOR_PLL_2_SEQ_PLLCAPPD;
|
||||
value |= SOR_PLL_2_SEQ_PLLCAPPD_ENFORCE;
|
||||
tegra_sor_writel(sor, value, SOR_PLL_2);
|
||||
|
||||
usleep_range(20, 100);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_output_sor_disable(struct tegra_output *output)
|
||||
{
|
||||
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
|
||||
struct tegra_sor *sor = to_sor(output);
|
||||
unsigned long value;
|
||||
int err = 0;
|
||||
u32 value;
|
||||
int err;
|
||||
|
||||
mutex_lock(&sor->lock);
|
||||
|
||||
if (!sor->enabled)
|
||||
goto unlock;
|
||||
|
||||
if (output->panel)
|
||||
drm_panel_disable(output->panel);
|
||||
|
||||
err = tegra_sor_detach(sor);
|
||||
if (err < 0) {
|
||||
dev_err(sor->dev, "failed to detach SOR: %d\n", err);
|
||||
|
@ -1057,31 +1236,11 @@ static int tegra_output_sor_disable(struct tegra_output *output)
|
|||
* sure it's only executed when the output is attached to one.
|
||||
*/
|
||||
if (dc) {
|
||||
/*
|
||||
* XXX: We can't do this here because it causes the SOR to go
|
||||
* into an erroneous state and the output will look scrambled
|
||||
* the next time it is enabled. Presumably this is because we
|
||||
* should be doing this only on the next VBLANK. A possible
|
||||
* solution would be to queue a "power-off" event to trigger
|
||||
* this code to be run during the next VBLANK.
|
||||
*/
|
||||
/*
|
||||
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL);
|
||||
value &= ~(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
|
||||
PW4_ENABLE | PM0_ENABLE | PM1_ENABLE);
|
||||
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
|
||||
*/
|
||||
|
||||
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
|
||||
value &= ~DISP_CTRL_MODE_MASK;
|
||||
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
|
||||
|
||||
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
|
||||
value &= ~SOR_ENABLE;
|
||||
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
|
||||
|
||||
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
|
||||
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
|
||||
tegra_dc_commit(dc);
|
||||
}
|
||||
|
||||
err = tegra_sor_power_down(sor);
|
||||
|
@ -1104,187 +1263,48 @@ static int tegra_output_sor_disable(struct tegra_output *output)
|
|||
goto unlock;
|
||||
}
|
||||
|
||||
reset_control_assert(sor->rst);
|
||||
if (output->panel)
|
||||
drm_panel_unprepare(output->panel);
|
||||
|
||||
clk_disable_unprepare(sor->clk);
|
||||
reset_control_assert(sor->rst);
|
||||
|
||||
sor->enabled = false;
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&sor->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tegra_output_sor_setup_clock(struct tegra_output *output,
|
||||
struct clk *clk, unsigned long pclk,
|
||||
unsigned int *div)
|
||||
static int
|
||||
tegra_sor_encoder_atomic_check(struct drm_encoder *encoder,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
struct tegra_dc *dc = to_tegra_dc(conn_state->crtc);
|
||||
unsigned long pclk = crtc_state->mode.clock * 1000;
|
||||
struct tegra_sor *sor = to_sor(output);
|
||||
int err;
|
||||
|
||||
err = clk_set_parent(clk, sor->clk_parent);
|
||||
err = tegra_dc_state_setup_clock(dc, crtc_state, sor->clk_parent,
|
||||
pclk, 0);
|
||||
if (err < 0) {
|
||||
dev_err(sor->dev, "failed to set parent clock: %d\n", err);
|
||||
dev_err(output->dev, "failed to setup CRTC state: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = clk_set_rate(sor->clk_parent, pclk);
|
||||
if (err < 0) {
|
||||
dev_err(sor->dev, "failed to set clock rate to %lu Hz\n", pclk);
|
||||
return err;
|
||||
}
|
||||
|
||||
*div = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_output_sor_check_mode(struct tegra_output *output,
|
||||
struct drm_display_mode *mode,
|
||||
enum drm_mode_status *status)
|
||||
{
|
||||
/*
|
||||
* FIXME: For now, always assume that the mode is okay.
|
||||
*/
|
||||
|
||||
*status = MODE_OK;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum drm_connector_status
|
||||
tegra_output_sor_detect(struct tegra_output *output)
|
||||
{
|
||||
struct tegra_sor *sor = to_sor(output);
|
||||
|
||||
if (sor->dpaux)
|
||||
return tegra_dpaux_detect(sor->dpaux);
|
||||
|
||||
return connector_status_unknown;
|
||||
}
|
||||
|
||||
static const struct tegra_output_ops sor_ops = {
|
||||
.enable = tegra_output_sor_enable,
|
||||
.disable = tegra_output_sor_disable,
|
||||
.setup_clock = tegra_output_sor_setup_clock,
|
||||
.check_mode = tegra_output_sor_check_mode,
|
||||
.detect = tegra_output_sor_detect,
|
||||
static const struct drm_encoder_helper_funcs tegra_sor_encoder_helper_funcs = {
|
||||
.dpms = tegra_sor_encoder_dpms,
|
||||
.prepare = tegra_sor_encoder_prepare,
|
||||
.commit = tegra_sor_encoder_commit,
|
||||
.mode_set = tegra_sor_encoder_mode_set,
|
||||
.disable = tegra_sor_encoder_disable,
|
||||
.atomic_check = tegra_sor_encoder_atomic_check,
|
||||
};
|
||||
|
||||
static int tegra_sor_crc_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
file->private_data = inode->i_private;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_sor_crc_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_sor_crc_wait(struct tegra_sor *sor, unsigned long timeout)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
timeout = jiffies + msecs_to_jiffies(timeout);
|
||||
|
||||
while (time_before(jiffies, timeout)) {
|
||||
value = tegra_sor_readl(sor, SOR_CRC_A);
|
||||
if (value & SOR_CRC_A_VALID)
|
||||
return 0;
|
||||
|
||||
usleep_range(100, 200);
|
||||
}
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static ssize_t tegra_sor_crc_read(struct file *file, char __user *buffer,
|
||||
size_t size, loff_t *ppos)
|
||||
{
|
||||
struct tegra_sor *sor = file->private_data;
|
||||
ssize_t num, err;
|
||||
char buf[10];
|
||||
u32 value;
|
||||
|
||||
mutex_lock(&sor->lock);
|
||||
|
||||
if (!sor->enabled) {
|
||||
err = -EAGAIN;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_STATE_1);
|
||||
value &= ~SOR_STATE_ASY_CRC_MODE_MASK;
|
||||
tegra_sor_writel(sor, value, SOR_STATE_1);
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_CRC_CNTRL);
|
||||
value |= SOR_CRC_CNTRL_ENABLE;
|
||||
tegra_sor_writel(sor, value, SOR_CRC_CNTRL);
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_TEST);
|
||||
value &= ~SOR_TEST_CRC_POST_SERIALIZE;
|
||||
tegra_sor_writel(sor, value, SOR_TEST);
|
||||
|
||||
err = tegra_sor_crc_wait(sor, 100);
|
||||
if (err < 0)
|
||||
goto unlock;
|
||||
|
||||
tegra_sor_writel(sor, SOR_CRC_A_RESET, SOR_CRC_A);
|
||||
value = tegra_sor_readl(sor, SOR_CRC_B);
|
||||
|
||||
num = scnprintf(buf, sizeof(buf), "%08x\n", value);
|
||||
|
||||
err = simple_read_from_buffer(buffer, size, ppos, buf, num);
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&sor->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct file_operations tegra_sor_crc_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = tegra_sor_crc_open,
|
||||
.read = tegra_sor_crc_read,
|
||||
.release = tegra_sor_crc_release,
|
||||
};
|
||||
|
||||
static int tegra_sor_debugfs_init(struct tegra_sor *sor,
|
||||
struct drm_minor *minor)
|
||||
{
|
||||
struct dentry *entry;
|
||||
int err = 0;
|
||||
|
||||
sor->debugfs = debugfs_create_dir("sor", minor->debugfs_root);
|
||||
if (!sor->debugfs)
|
||||
return -ENOMEM;
|
||||
|
||||
entry = debugfs_create_file("crc", 0644, sor->debugfs, sor,
|
||||
&tegra_sor_crc_fops);
|
||||
if (!entry) {
|
||||
dev_err(sor->dev,
|
||||
"cannot create /sys/kernel/debug/dri/%s/sor/crc\n",
|
||||
minor->debugfs_root->d_name.name);
|
||||
err = -ENOMEM;
|
||||
goto remove;
|
||||
}
|
||||
|
||||
return err;
|
||||
|
||||
remove:
|
||||
debugfs_remove(sor->debugfs);
|
||||
sor->debugfs = NULL;
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tegra_sor_debugfs_exit(struct tegra_sor *sor)
|
||||
{
|
||||
debugfs_remove_recursive(sor->debugfs);
|
||||
sor->debugfs = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_sor_init(struct host1x_client *client)
|
||||
{
|
||||
struct drm_device *drm = dev_get_drvdata(client->parent);
|
||||
|
@ -1294,17 +1314,32 @@ static int tegra_sor_init(struct host1x_client *client)
|
|||
if (!sor->dpaux)
|
||||
return -ENODEV;
|
||||
|
||||
sor->output.type = TEGRA_OUTPUT_EDP;
|
||||
|
||||
sor->output.dev = sor->dev;
|
||||
sor->output.ops = &sor_ops;
|
||||
|
||||
drm_connector_init(drm, &sor->output.connector,
|
||||
&tegra_sor_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_eDP);
|
||||
drm_connector_helper_add(&sor->output.connector,
|
||||
&tegra_sor_connector_helper_funcs);
|
||||
sor->output.connector.dpms = DRM_MODE_DPMS_OFF;
|
||||
|
||||
drm_encoder_init(drm, &sor->output.encoder, &tegra_sor_encoder_funcs,
|
||||
DRM_MODE_ENCODER_TMDS);
|
||||
drm_encoder_helper_add(&sor->output.encoder,
|
||||
&tegra_sor_encoder_helper_funcs);
|
||||
|
||||
drm_mode_connector_attach_encoder(&sor->output.connector,
|
||||
&sor->output.encoder);
|
||||
drm_connector_register(&sor->output.connector);
|
||||
|
||||
err = tegra_output_init(drm, &sor->output);
|
||||
if (err < 0) {
|
||||
dev_err(sor->dev, "output setup failed: %d\n", err);
|
||||
dev_err(client->dev, "failed to initialize output: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
sor->output.encoder.possible_crtcs = 0x3;
|
||||
|
||||
if (IS_ENABLED(CONFIG_DEBUG_FS)) {
|
||||
err = tegra_sor_debugfs_init(sor, drm->primary);
|
||||
if (err < 0)
|
||||
|
@ -1319,6 +1354,20 @@ static int tegra_sor_init(struct host1x_client *client)
|
|||
}
|
||||
}
|
||||
|
||||
err = clk_prepare_enable(sor->clk);
|
||||
if (err < 0) {
|
||||
dev_err(sor->dev, "failed to enable clock: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = clk_prepare_enable(sor->clk_safe);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = clk_prepare_enable(sor->clk_dp);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1327,11 +1376,7 @@ static int tegra_sor_exit(struct host1x_client *client)
|
|||
struct tegra_sor *sor = host1x_client_to_sor(client);
|
||||
int err;
|
||||
|
||||
err = tegra_output_disable(&sor->output);
|
||||
if (err < 0) {
|
||||
dev_err(sor->dev, "output failed to disable: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
tegra_output_exit(&sor->output);
|
||||
|
||||
if (sor->dpaux) {
|
||||
err = tegra_dpaux_detach(sor->dpaux);
|
||||
|
@ -1341,17 +1386,12 @@ static int tegra_sor_exit(struct host1x_client *client)
|
|||
}
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_DEBUG_FS)) {
|
||||
err = tegra_sor_debugfs_exit(sor);
|
||||
if (err < 0)
|
||||
dev_err(sor->dev, "debugfs cleanup failed: %d\n", err);
|
||||
}
|
||||
clk_disable_unprepare(sor->clk_safe);
|
||||
clk_disable_unprepare(sor->clk_dp);
|
||||
clk_disable_unprepare(sor->clk);
|
||||
|
||||
err = tegra_output_exit(&sor->output);
|
||||
if (err < 0) {
|
||||
dev_err(sor->dev, "output cleanup failed: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
if (IS_ENABLED(CONFIG_DEBUG_FS))
|
||||
tegra_sor_debugfs_exit(sor);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1404,26 +1444,14 @@ static int tegra_sor_probe(struct platform_device *pdev)
|
|||
if (IS_ERR(sor->clk_parent))
|
||||
return PTR_ERR(sor->clk_parent);
|
||||
|
||||
err = clk_prepare_enable(sor->clk_parent);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
sor->clk_safe = devm_clk_get(&pdev->dev, "safe");
|
||||
if (IS_ERR(sor->clk_safe))
|
||||
return PTR_ERR(sor->clk_safe);
|
||||
|
||||
err = clk_prepare_enable(sor->clk_safe);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
sor->clk_dp = devm_clk_get(&pdev->dev, "dp");
|
||||
if (IS_ERR(sor->clk_dp))
|
||||
return PTR_ERR(sor->clk_dp);
|
||||
|
||||
err = clk_prepare_enable(sor->clk_dp);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
INIT_LIST_HEAD(&sor->client.list);
|
||||
sor->client.ops = &sor_client_ops;
|
||||
sor->client.dev = &pdev->dev;
|
||||
|
@ -1454,10 +1482,7 @@ static int tegra_sor_remove(struct platform_device *pdev)
|
|||
return err;
|
||||
}
|
||||
|
||||
clk_disable_unprepare(sor->clk_parent);
|
||||
clk_disable_unprepare(sor->clk_safe);
|
||||
clk_disable_unprepare(sor->clk_dp);
|
||||
clk_disable_unprepare(sor->clk);
|
||||
tegra_output_remove(&sor->output);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -72,13 +72,14 @@ static void host1x_subdev_del(struct host1x_subdev *subdev)
|
|||
/**
|
||||
* host1x_device_parse_dt() - scan device tree and add matching subdevices
|
||||
*/
|
||||
static int host1x_device_parse_dt(struct host1x_device *device)
|
||||
static int host1x_device_parse_dt(struct host1x_device *device,
|
||||
struct host1x_driver *driver)
|
||||
{
|
||||
struct device_node *np;
|
||||
int err;
|
||||
|
||||
for_each_child_of_node(device->dev.parent->of_node, np) {
|
||||
if (of_match_node(device->driver->subdevs, np) &&
|
||||
if (of_match_node(driver->subdevs, np) &&
|
||||
of_device_is_available(np)) {
|
||||
err = host1x_subdev_add(device, np);
|
||||
if (err < 0)
|
||||
|
@ -109,14 +110,12 @@ static void host1x_subdev_register(struct host1x_device *device,
|
|||
mutex_unlock(&device->clients_lock);
|
||||
mutex_unlock(&device->subdevs_lock);
|
||||
|
||||
/*
|
||||
* When all subdevices have been registered, the composite device is
|
||||
* ready to be probed.
|
||||
*/
|
||||
if (list_empty(&device->subdevs)) {
|
||||
err = device->driver->probe(device);
|
||||
err = device_add(&device->dev);
|
||||
if (err < 0)
|
||||
dev_err(&device->dev, "probe failed: %d\n", err);
|
||||
dev_err(&device->dev, "failed to add: %d\n", err);
|
||||
else
|
||||
device->registered = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,16 +123,16 @@ static void __host1x_subdev_unregister(struct host1x_device *device,
|
|||
struct host1x_subdev *subdev)
|
||||
{
|
||||
struct host1x_client *client = subdev->client;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* If all subdevices have been activated, we're about to remove the
|
||||
* first active subdevice, so unload the driver first.
|
||||
*/
|
||||
if (list_empty(&device->subdevs)) {
|
||||
err = device->driver->remove(device);
|
||||
if (err < 0)
|
||||
dev_err(&device->dev, "remove failed: %d\n", err);
|
||||
if (device->registered) {
|
||||
device->registered = false;
|
||||
device_del(&device->dev);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -260,92 +259,61 @@ static int host1x_del_client(struct host1x *host1x,
|
|||
return -ENODEV;
|
||||
}
|
||||
|
||||
static struct bus_type host1x_bus_type = {
|
||||
.name = "host1x",
|
||||
};
|
||||
|
||||
int host1x_bus_init(void)
|
||||
static int host1x_device_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
return bus_register(&host1x_bus_type);
|
||||
return strcmp(dev_name(dev), drv->name) == 0;
|
||||
}
|
||||
|
||||
void host1x_bus_exit(void)
|
||||
{
|
||||
bus_unregister(&host1x_bus_type);
|
||||
}
|
||||
|
||||
static void host1x_device_release(struct device *dev)
|
||||
static int host1x_device_probe(struct device *dev)
|
||||
{
|
||||
struct host1x_driver *driver = to_host1x_driver(dev->driver);
|
||||
struct host1x_device *device = to_host1x_device(dev);
|
||||
|
||||
kfree(device);
|
||||
}
|
||||
|
||||
static int host1x_device_add(struct host1x *host1x,
|
||||
struct host1x_driver *driver)
|
||||
{
|
||||
struct host1x_client *client, *tmp;
|
||||
struct host1x_subdev *subdev;
|
||||
struct host1x_device *device;
|
||||
int err;
|
||||
|
||||
device = kzalloc(sizeof(*device), GFP_KERNEL);
|
||||
if (!device)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&device->subdevs_lock);
|
||||
INIT_LIST_HEAD(&device->subdevs);
|
||||
INIT_LIST_HEAD(&device->active);
|
||||
mutex_init(&device->clients_lock);
|
||||
INIT_LIST_HEAD(&device->clients);
|
||||
INIT_LIST_HEAD(&device->list);
|
||||
device->driver = driver;
|
||||
|
||||
device->dev.coherent_dma_mask = host1x->dev->coherent_dma_mask;
|
||||
device->dev.dma_mask = &device->dev.coherent_dma_mask;
|
||||
device->dev.release = host1x_device_release;
|
||||
dev_set_name(&device->dev, "%s", driver->name);
|
||||
device->dev.bus = &host1x_bus_type;
|
||||
device->dev.parent = host1x->dev;
|
||||
|
||||
err = device_register(&device->dev);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = host1x_device_parse_dt(device);
|
||||
if (err < 0) {
|
||||
device_unregister(&device->dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
mutex_lock(&host1x->devices_lock);
|
||||
list_add_tail(&device->list, &host1x->devices);
|
||||
mutex_unlock(&host1x->devices_lock);
|
||||
|
||||
mutex_lock(&clients_lock);
|
||||
|
||||
list_for_each_entry_safe(client, tmp, &clients, list) {
|
||||
list_for_each_entry(subdev, &device->subdevs, list) {
|
||||
if (subdev->np == client->dev->of_node) {
|
||||
host1x_subdev_register(device, subdev, client);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&clients_lock);
|
||||
if (driver->probe)
|
||||
return driver->probe(device);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes a device by first unregistering any subdevices and then removing
|
||||
* itself from the list of devices.
|
||||
*
|
||||
* This function must be called with the host1x->devices_lock held.
|
||||
*/
|
||||
static void host1x_device_del(struct host1x *host1x,
|
||||
struct host1x_device *device)
|
||||
static int host1x_device_remove(struct device *dev)
|
||||
{
|
||||
struct host1x_driver *driver = to_host1x_driver(dev->driver);
|
||||
struct host1x_device *device = to_host1x_device(dev);
|
||||
|
||||
if (driver->remove)
|
||||
return driver->remove(device);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void host1x_device_shutdown(struct device *dev)
|
||||
{
|
||||
struct host1x_driver *driver = to_host1x_driver(dev->driver);
|
||||
struct host1x_device *device = to_host1x_device(dev);
|
||||
|
||||
if (driver->shutdown)
|
||||
driver->shutdown(device);
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops host1x_device_pm_ops = {
|
||||
.suspend = pm_generic_suspend,
|
||||
.resume = pm_generic_resume,
|
||||
.freeze = pm_generic_freeze,
|
||||
.thaw = pm_generic_thaw,
|
||||
.poweroff = pm_generic_poweroff,
|
||||
.restore = pm_generic_restore,
|
||||
};
|
||||
|
||||
struct bus_type host1x_bus_type = {
|
||||
.name = "host1x",
|
||||
.match = host1x_device_match,
|
||||
.probe = host1x_device_probe,
|
||||
.remove = host1x_device_remove,
|
||||
.shutdown = host1x_device_shutdown,
|
||||
.pm = &host1x_device_pm_ops,
|
||||
};
|
||||
|
||||
static void __host1x_device_del(struct host1x_device *device)
|
||||
{
|
||||
struct host1x_subdev *subdev, *sd;
|
||||
struct host1x_client *client, *cl;
|
||||
|
@ -391,7 +359,84 @@ static void host1x_device_del(struct host1x *host1x,
|
|||
|
||||
/* finally remove the device */
|
||||
list_del_init(&device->list);
|
||||
device_unregister(&device->dev);
|
||||
}
|
||||
|
||||
static void host1x_device_release(struct device *dev)
|
||||
{
|
||||
struct host1x_device *device = to_host1x_device(dev);
|
||||
|
||||
__host1x_device_del(device);
|
||||
kfree(device);
|
||||
}
|
||||
|
||||
static int host1x_device_add(struct host1x *host1x,
|
||||
struct host1x_driver *driver)
|
||||
{
|
||||
struct host1x_client *client, *tmp;
|
||||
struct host1x_subdev *subdev;
|
||||
struct host1x_device *device;
|
||||
int err;
|
||||
|
||||
device = kzalloc(sizeof(*device), GFP_KERNEL);
|
||||
if (!device)
|
||||
return -ENOMEM;
|
||||
|
||||
device_initialize(&device->dev);
|
||||
|
||||
mutex_init(&device->subdevs_lock);
|
||||
INIT_LIST_HEAD(&device->subdevs);
|
||||
INIT_LIST_HEAD(&device->active);
|
||||
mutex_init(&device->clients_lock);
|
||||
INIT_LIST_HEAD(&device->clients);
|
||||
INIT_LIST_HEAD(&device->list);
|
||||
device->driver = driver;
|
||||
|
||||
device->dev.coherent_dma_mask = host1x->dev->coherent_dma_mask;
|
||||
device->dev.dma_mask = &device->dev.coherent_dma_mask;
|
||||
dev_set_name(&device->dev, "%s", driver->driver.name);
|
||||
device->dev.release = host1x_device_release;
|
||||
device->dev.bus = &host1x_bus_type;
|
||||
device->dev.parent = host1x->dev;
|
||||
|
||||
err = host1x_device_parse_dt(device, driver);
|
||||
if (err < 0) {
|
||||
kfree(device);
|
||||
return err;
|
||||
}
|
||||
|
||||
list_add_tail(&device->list, &host1x->devices);
|
||||
|
||||
mutex_lock(&clients_lock);
|
||||
|
||||
list_for_each_entry_safe(client, tmp, &clients, list) {
|
||||
list_for_each_entry(subdev, &device->subdevs, list) {
|
||||
if (subdev->np == client->dev->of_node) {
|
||||
host1x_subdev_register(device, subdev, client);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&clients_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes a device by first unregistering any subdevices and then removing
|
||||
* itself from the list of devices.
|
||||
*
|
||||
* This function must be called with the host1x->devices_lock held.
|
||||
*/
|
||||
static void host1x_device_del(struct host1x *host1x,
|
||||
struct host1x_device *device)
|
||||
{
|
||||
if (device->registered) {
|
||||
device->registered = false;
|
||||
device_del(&device->dev);
|
||||
}
|
||||
|
||||
put_device(&device->dev);
|
||||
}
|
||||
|
||||
static void host1x_attach_driver(struct host1x *host1x,
|
||||
|
@ -409,11 +454,11 @@ static void host1x_attach_driver(struct host1x *host1x,
|
|||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&host1x->devices_lock);
|
||||
|
||||
err = host1x_device_add(host1x, driver);
|
||||
if (err < 0)
|
||||
dev_err(host1x->dev, "failed to allocate device: %d\n", err);
|
||||
|
||||
mutex_unlock(&host1x->devices_lock);
|
||||
}
|
||||
|
||||
static void host1x_detach_driver(struct host1x *host1x,
|
||||
|
@ -466,7 +511,8 @@ int host1x_unregister(struct host1x *host1x)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int host1x_driver_register(struct host1x_driver *driver)
|
||||
int host1x_driver_register_full(struct host1x_driver *driver,
|
||||
struct module *owner)
|
||||
{
|
||||
struct host1x *host1x;
|
||||
|
||||
|
@ -483,9 +529,12 @@ int host1x_driver_register(struct host1x_driver *driver)
|
|||
|
||||
mutex_unlock(&devices_lock);
|
||||
|
||||
return 0;
|
||||
driver->driver.bus = &host1x_bus_type;
|
||||
driver->driver.owner = owner;
|
||||
|
||||
return driver_register(&driver->driver);
|
||||
}
|
||||
EXPORT_SYMBOL(host1x_driver_register);
|
||||
EXPORT_SYMBOL(host1x_driver_register_full);
|
||||
|
||||
void host1x_driver_unregister(struct host1x_driver *driver)
|
||||
{
|
||||
|
|
|
@ -18,10 +18,10 @@
|
|||
#ifndef HOST1X_BUS_H
|
||||
#define HOST1X_BUS_H
|
||||
|
||||
struct bus_type;
|
||||
struct host1x;
|
||||
|
||||
int host1x_bus_init(void);
|
||||
void host1x_bus_exit(void);
|
||||
extern struct bus_type host1x_bus_type;
|
||||
|
||||
int host1x_register(struct host1x *host1x);
|
||||
int host1x_unregister(struct host1x *host1x);
|
||||
|
|
|
@ -216,7 +216,7 @@ static int __init tegra_host1x_init(void)
|
|||
{
|
||||
int err;
|
||||
|
||||
err = host1x_bus_init();
|
||||
err = bus_register(&host1x_bus_type);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
|
@ -233,7 +233,7 @@ static int __init tegra_host1x_init(void)
|
|||
unregister_host1x:
|
||||
platform_driver_unregister(&tegra_host1x_driver);
|
||||
unregister_bus:
|
||||
host1x_bus_exit();
|
||||
bus_unregister(&host1x_bus_type);
|
||||
return err;
|
||||
}
|
||||
module_init(tegra_host1x_init);
|
||||
|
@ -242,7 +242,7 @@ static void __exit tegra_host1x_exit(void)
|
|||
{
|
||||
platform_driver_unregister(&tegra_mipi_driver);
|
||||
platform_driver_unregister(&tegra_host1x_driver);
|
||||
host1x_bus_exit();
|
||||
bus_unregister(&host1x_bus_type);
|
||||
}
|
||||
module_exit(tegra_host1x_exit);
|
||||
|
||||
|
|
|
@ -127,4 +127,41 @@ void drm_atomic_helper_connector_destroy_state(struct drm_connector *connector,
|
|||
#define drm_atomic_crtc_state_for_each_plane(plane, crtc_state) \
|
||||
drm_for_each_plane_mask(plane, (crtc_state)->state->dev, (crtc_state)->plane_mask)
|
||||
|
||||
/*
|
||||
* drm_atomic_plane_disabling - check whether a plane is being disabled
|
||||
* @plane: plane object
|
||||
* @old_state: previous atomic state
|
||||
*
|
||||
* Checks the atomic state of a plane to determine whether it's being disabled
|
||||
* or not. This also WARNs if it detects an invalid state (both CRTC and FB
|
||||
* need to either both be NULL or both be non-NULL).
|
||||
*
|
||||
* RETURNS:
|
||||
* True if the plane is being disabled, false otherwise.
|
||||
*/
|
||||
static inline bool
|
||||
drm_atomic_plane_disabling(struct drm_plane *plane,
|
||||
struct drm_plane_state *old_state)
|
||||
{
|
||||
/*
|
||||
* When disabling a plane, CRTC and FB should always be NULL together.
|
||||
* Anything else should be considered a bug in the atomic core, so we
|
||||
* gently warn about it.
|
||||
*/
|
||||
WARN_ON((plane->state->crtc == NULL && plane->state->fb != NULL) ||
|
||||
(plane->state->crtc != NULL && plane->state->fb == NULL));
|
||||
|
||||
/*
|
||||
* When using the transitional helpers, old_state may be NULL. If so,
|
||||
* we know nothing about the current state and have to assume that it
|
||||
* might be enabled.
|
||||
*
|
||||
* When using the atomic helpers, old_state won't be NULL. Therefore
|
||||
* this check assumes that either the driver will have reconstructed
|
||||
* the correct state in ->reset() or that the driver will have taken
|
||||
* appropriate measures to disable all planes.
|
||||
*/
|
||||
return (!old_state || old_state->crtc) && !plane->state->crtc;
|
||||
}
|
||||
|
||||
#endif /* DRM_ATOMIC_HELPER_H_ */
|
||||
|
|
|
@ -115,6 +115,7 @@ struct drm_crtc_helper_funcs {
|
|||
* @get_crtc: return CRTC that the encoder is currently attached to
|
||||
* @detect: connection status detection
|
||||
* @disable: disable encoder when not in use (overrides DPMS off)
|
||||
* @atomic_check: check for validity of an atomic update
|
||||
*
|
||||
* The helper operations are called by the mid-layer CRTC helper.
|
||||
*/
|
||||
|
@ -137,6 +138,11 @@ struct drm_encoder_helper_funcs {
|
|||
struct drm_connector *connector);
|
||||
/* disable encoder when not in use - more explicit than dpms off */
|
||||
void (*disable)(struct drm_encoder *encoder);
|
||||
|
||||
/* atomic helpers */
|
||||
int (*atomic_check)(struct drm_encoder *encoder,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -52,7 +52,8 @@ extern int drm_crtc_init(struct drm_device *dev,
|
|||
* @prepare_fb: prepare a framebuffer for use by the plane
|
||||
* @cleanup_fb: cleanup a framebuffer when it's no longer used by the plane
|
||||
* @atomic_check: check that a given atomic state is valid and can be applied
|
||||
* @atomic_update: apply an atomic state to the plane
|
||||
* @atomic_update: apply an atomic state to the plane (mandatory)
|
||||
* @atomic_disable: disable the plane
|
||||
*
|
||||
* The helper operations are called by the mid-layer CRTC helper.
|
||||
*/
|
||||
|
@ -66,6 +67,8 @@ struct drm_plane_helper_funcs {
|
|||
struct drm_plane_state *state);
|
||||
void (*atomic_update)(struct drm_plane *plane,
|
||||
struct drm_plane_state *old_state);
|
||||
void (*atomic_disable)(struct drm_plane *plane,
|
||||
struct drm_plane_state *old_state);
|
||||
};
|
||||
|
||||
static inline void drm_plane_helper_add(struct drm_plane *plane,
|
||||
|
|
|
@ -250,17 +250,29 @@ void host1x_job_unpin(struct host1x_job *job);
|
|||
struct host1x_device;
|
||||
|
||||
struct host1x_driver {
|
||||
struct device_driver driver;
|
||||
|
||||
const struct of_device_id *subdevs;
|
||||
struct list_head list;
|
||||
const char *name;
|
||||
|
||||
int (*probe)(struct host1x_device *device);
|
||||
int (*remove)(struct host1x_device *device);
|
||||
void (*shutdown)(struct host1x_device *device);
|
||||
};
|
||||
|
||||
int host1x_driver_register(struct host1x_driver *driver);
|
||||
static inline struct host1x_driver *
|
||||
to_host1x_driver(struct device_driver *driver)
|
||||
{
|
||||
return container_of(driver, struct host1x_driver, driver);
|
||||
}
|
||||
|
||||
int host1x_driver_register_full(struct host1x_driver *driver,
|
||||
struct module *owner);
|
||||
void host1x_driver_unregister(struct host1x_driver *driver);
|
||||
|
||||
#define host1x_driver_register(driver) \
|
||||
host1x_driver_register_full(driver, THIS_MODULE)
|
||||
|
||||
struct host1x_device {
|
||||
struct host1x_driver *driver;
|
||||
struct list_head list;
|
||||
|
@ -272,6 +284,8 @@ struct host1x_device {
|
|||
|
||||
struct mutex clients_lock;
|
||||
struct list_head clients;
|
||||
|
||||
bool registered;
|
||||
};
|
||||
|
||||
static inline struct host1x_device *to_host1x_device(struct device *dev)
|
||||
|
|
Loading…
Reference in New Issue