Commit 2f5b4ef1 authored by Dave Airlie's avatar Dave Airlie

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
  ...
parents 1da30627 31f40f86
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-$(CONFIG_TEGRA_HOST1X) += host1x/
obj-y += drm/ vga/
obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/ obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/
...@@ -297,13 +297,22 @@ mode_fixup(struct drm_atomic_state *state) ...@@ -297,13 +297,22 @@ mode_fixup(struct drm_atomic_state *state)
} }
} }
if (funcs->atomic_check) {
ret = funcs->mode_fixup(encoder, &crtc_state->mode, ret = funcs->atomic_check(encoder, crtc_state,
&crtc_state->adjusted_mode); conn_state);
if (!ret) { if (ret) {
DRM_DEBUG_KMS("[ENCODER:%d:%s] fixup failed\n", DRM_DEBUG_KMS("[ENCODER:%d:%s] check failed\n",
encoder->base.id, encoder->name); encoder->base.id, encoder->name);
return -EINVAL; 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, ...@@ -1108,12 +1117,19 @@ void drm_atomic_helper_commit_planes(struct drm_device *dev,
funcs = plane->helper_private; funcs = plane->helper_private;
if (!funcs || !funcs->atomic_update) if (!funcs)
continue; continue;
old_plane_state = old_state->plane_states[i]; 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++) { for (i = 0; i < ncrtcs; i++) {
......
...@@ -449,7 +449,15 @@ int drm_plane_helper_commit(struct drm_plane *plane, ...@@ -449,7 +449,15 @@ int drm_plane_helper_commit(struct drm_plane *plane,
crtc_funcs[i]->atomic_begin(crtc[i]); 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++) { for (i = 0; i < 2; i++) {
if (crtc_funcs[i] && crtc_funcs[i]->atomic_flush) if (crtc_funcs[i] && crtc_funcs[i]->atomic_flush)
......
This diff is collapsed.
...@@ -10,6 +10,9 @@ ...@@ -10,6 +10,9 @@
#include <linux/host1x.h> #include <linux/host1x.h>
#include <linux/iommu.h> #include <linux/iommu.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include "drm.h" #include "drm.h"
#include "gem.h" #include "gem.h"
...@@ -24,6 +27,92 @@ struct tegra_drm_file { ...@@ -24,6 +27,92 @@ struct tegra_drm_file {
struct list_head contexts; 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) static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
{ {
struct host1x_device *device = to_host1x_device(drm->dev); 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) ...@@ -36,8 +125,8 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
if (iommu_present(&platform_bus_type)) { if (iommu_present(&platform_bus_type)) {
tegra->domain = iommu_domain_alloc(&platform_bus_type); tegra->domain = iommu_domain_alloc(&platform_bus_type);
if (IS_ERR(tegra->domain)) { if (!tegra->domain) {
err = PTR_ERR(tegra->domain); err = -ENOMEM;
goto free; goto free;
} }
...@@ -47,11 +136,23 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) ...@@ -47,11 +136,23 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
mutex_init(&tegra->clients_lock); mutex_init(&tegra->clients_lock);
INIT_LIST_HEAD(&tegra->clients); INIT_LIST_HEAD(&tegra->clients);
mutex_init(&tegra->commit.lock);
INIT_WORK(&tegra->commit.work, tegra_atomic_work);
drm->dev_private = tegra; drm->dev_private = tegra;
tegra->drm = drm; tegra->drm = drm;
drm_mode_config_init(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); err = tegra_drm_fb_prepare(drm);
if (err < 0) if (err < 0)
goto config; goto config;
...@@ -62,6 +163,8 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) ...@@ -62,6 +163,8 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
if (err < 0) if (err < 0)
goto fbdev; goto fbdev;
drm_mode_config_reset(drm);
/* /*
* We don't use the drm_irq_install() helpers provided by the 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 * 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) ...@@ -106,8 +209,8 @@ static int tegra_drm_unload(struct drm_device *drm)
drm_kms_helper_poll_fini(drm); drm_kms_helper_poll_fini(drm);
tegra_drm_fb_exit(drm); tegra_drm_fb_exit(drm);
drm_vblank_cleanup(drm);
drm_mode_config_cleanup(drm); drm_mode_config_cleanup(drm);
drm_vblank_cleanup(drm);
err = host1x_device_exit(device); err = host1x_device_exit(device);
if (err < 0) if (err < 0)
...@@ -190,7 +293,7 @@ static int host1x_reloc_copy_from_user(struct host1x_reloc *dest, ...@@ -190,7 +293,7 @@ static int host1x_reloc_copy_from_user(struct host1x_reloc *dest,
if (err < 0) if (err < 0)
return err; return err;
err = get_user(dest->target.offset, &src->cmdbuf.offset); err = get_user(dest->target.offset, &src->target.offset);
if (err < 0) if (err < 0)
return err; return err;
...@@ -893,6 +996,30 @@ static int host1x_drm_remove(struct host1x_device *dev) ...@@ -893,6 +996,30 @@ static int host1x_drm_remove(struct host1x_device *dev)
return 0; 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[] = { static const struct of_device_id host1x_drm_subdevs[] = {
{ .compatible = "nvidia,tegra20-dc", }, { .compatible = "nvidia,tegra20-dc", },
{ .compatible = "nvidia,tegra20-hdmi", }, { .compatible = "nvidia,tegra20-hdmi", },
...@@ -912,7 +1039,10 @@ static const struct of_device_id host1x_drm_subdevs[] = { ...@@ -912,7 +1039,10 @@ static const struct of_device_id host1x_drm_subdevs[] = {
}; };
static struct host1x_driver host1x_drm_driver = { static struct host1x_driver host1x_drm_driver = {
.name = "drm", .driver = {
.name = "drm",
.pm = &host1x_drm_pm_ops,
},
.probe = host1x_drm_probe, .probe = host1x_drm_probe,
.remove = host1x_drm_remove, .remove = host1x_drm_remove,
.subdevs = host1x_drm_subdevs, .subdevs = host1x_drm_subdevs,
......
...@@ -50,6 +50,12 @@ struct tegra_drm { ...@@ -50,6 +50,12 @@ struct tegra_drm {
#endif #endif
unsigned int pitch_align; unsigned int pitch_align;
struct {
struct drm_atomic_state *state;
struct work_struct work;
struct mutex lock;
} commit;
}; };
struct tegra_drm_client; struct tegra_drm_client;
...@@ -164,45 +170,31 @@ struct tegra_dc_window { ...@@ -164,45 +170,31 @@ struct tegra_dc_window {
unsigned int h; unsigned int h;
} dst; } dst;
unsigned int bits_per_pixel; unsigned int bits_per_pixel;
unsigned int format;
unsigned int swap;
unsigned int stride[2]; unsigned int stride[2];
unsigned long base[3]; unsigned long base[3];
bool bottom_up; bool bottom_up;
struct tegra_bo_tiling tiling; struct tegra_bo_tiling tiling;
u32 format;
u32 swap;
}; };
/* from dc.c */ /* from dc.c */
void tegra_dc_enable_vblank(struct tegra_dc *dc); void tegra_dc_enable_vblank(struct tegra_dc *dc);
void tegra_dc_disable_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); void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file);
void tegra_dc_commit(struct tegra_dc *dc);
struct tegra_output_ops { int tegra_dc_setup_clock(struct tegra_dc *dc, struct clk *parent,
int (*enable)(struct tegra_output *output); unsigned long pclk, unsigned int div);
int (*disable)(struct tegra_output *output); int tegra_dc_state_setup_clock(struct tegra_dc *dc,
int (*setup_clock)(struct tegra_output *output, struct clk *clk, struct drm_crtc_state *crtc_state,
unsigned long pclk, unsigned int *div); struct clk *clk, unsigned long pclk,
int (*check_mode)(struct tegra_output *output, unsigned int div);
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,
};
struct tegra_output { struct tegra_output {
struct device_node *of_node; struct device_node *of_node;
struct device *dev; struct device *dev;
const struct tegra_output_ops *ops;
enum tegra_output_type type;
struct drm_panel *panel; struct drm_panel *panel;
struct i2c_adapter *ddc; struct i2c_adapter *ddc;
const struct edid *edid; const struct edid *edid;
...@@ -223,42 +215,6 @@ static inline struct tegra_output *connector_to_output(struct drm_connector *c) ...@@ -223,42 +215,6 @@ static inline struct tegra_output *connector_to_output(struct drm_connector *c)
return container_of(c, struct tegra_output, connector); 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 */ /* from rgb.c */
int tegra_dc_rgb_probe(struct tegra_dc *dc); int tegra_dc_rgb_probe(struct tegra_dc *dc);
int tegra_dc_rgb_remove(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); ...@@ -267,9 +223,18 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc);
/* from output.c */ /* from output.c */
int tegra_output_probe(struct tegra_output *output); 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_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 */ /* from dpaux.c */
struct tegra_dpaux; struct tegra_dpaux;
...@@ -291,12 +256,16 @@ struct tegra_bo *tegra_fb_get_plane(struct drm_framebuffer *framebuffer, ...@@ -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); bool tegra_fb_is_bottom_up(struct drm_framebuffer *framebuffer);
int tegra_fb_get_tiling(struct drm_framebuffer *framebuffer, int tegra_fb_get_tiling(struct drm_framebuffer *framebuffer,
struct tegra_bo_tiling *tiling); 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); int tegra_drm_fb_prepare(struct drm_device *drm);
void tegra_drm_fb_free(struct drm_device *drm); void tegra_drm_fb_free(struct drm_device *drm);
int tegra_drm_fb_init(struct drm_device *drm); int tegra_drm_fb_init(struct drm_device *drm);
void tegra_drm_fb_exit(struct drm_device *drm); void tegra_drm_fb_exit(struct drm_device *drm);
#ifdef CONFIG_DRM_TEGRA_FBDEV #ifdef CONFIG_DRM_TEGRA_FBDEV
void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev); void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev);
void tegra_fb_output_poll_changed(struct drm_device *drm);
#endif #endif
extern struct platform_driver tegra_dc_driver; extern struct platform_driver tegra_dc_driver;
......
This diff is collapsed.
...@@ -129,9 +129,9 @@ static struct tegra_fb *tegra_fb_alloc(struct drm_device *drm, ...@@ -129,9 +129,9 @@ static struct tegra_fb *tegra_fb_alloc(struct drm_device *drm,
return fb; return fb;
} }
static struct drm_framebuffer *tegra_fb_create(struct drm_device *drm, struct drm_framebuffer *tegra_fb_create(struct drm_device *drm,
struct drm_file *file, struct drm_file *file,
struct drm_mode_fb_cmd2 *cmd) struct drm_mode_fb_cmd2 *cmd)
{ {
unsigned int hsub, vsub, i; unsigned int hsub, vsub, i;
struct tegra_bo *planes[4]; struct tegra_bo *planes[4];
...@@ -377,7 +377,7 @@ void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev) ...@@ -377,7 +377,7 @@ void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev)
drm_fb_helper_restore_fbdev_mode_unlocked(&fbdev->base); 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; struct tegra_drm *tegra = drm->dev_private;
...@@ -386,28 +386,11 @@ static void tegra_fb_output_poll_changed(struct drm_device *drm) ...@@ -386,28 +386,11 @@ static void tegra_fb_output_poll_changed(struct drm_device *drm)
} }
#endif #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) int tegra_drm_fb_prepare(struct drm_device *drm)
{ {
#ifdef CONFIG_DRM_TEGRA_FBDEV #ifdef CONFIG_DRM_TEGRA_FBDEV
struct tegra_drm *tegra = drm->dev_private; 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); tegra->fbdev = tegra_fbdev_create(drm);
if (IS_ERR(tegra->fbdev)) if (IS_ERR(tegra->fbdev))
return PTR_ERR(tegra->fbdev); return PTR_ERR(tegra->fbdev);
......
...@@ -92,36 +92,6 @@ static const struct host1x_bo_ops tegra_bo_ops = { ...@@ -92,36 +92,6 @@ static const struct host1x_bo_ops tegra_bo_ops = {
.kunmap = tegra_bo_kunmap, .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) static int tegra_bo_iommu_map(struct tegra_drm *tegra, struct tegra_bo *bo)
{ {
int prot = IOMMU_READ | IOMMU_WRITE; int prot = IOMMU_READ | IOMMU_WRITE;
...@@ -144,8 +114,8 @@ static int tegra_bo_iommu_map(struct tegra_drm *tegra, struct tegra_bo *bo) ...@@ -144,8 +114,8 @@ static int tegra_bo_iommu_map(struct tegra_drm *tegra, struct tegra_bo *bo)
bo->paddr = bo->mm->start; bo->paddr = bo->mm->start;
err = __iommu_map_sg(tegra->domain, bo->paddr, bo->sgt->sgl, err = iommu_map_sg(tegra->domain, bo->paddr, bo->sgt->sgl,
bo->sgt->nents, prot); bo->sgt->nents, prot);
if (err < 0) { if (err < 0) {
dev_err(tegra->drm->dev, "failed to map buffer: %zd\n", err); dev_err(tegra->drm->dev, "failed to map buffer: %zd\n", err);
goto remove; goto remove;
...@@ -244,10 +214,8 @@ static int tegra_bo_get_pages(struct drm_device *drm, struct tegra_bo *bo) ...@@ -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) for_each_sg(sgt->sgl, s, sgt->nents, i)
sg_dma_address(s) = sg_phys(s); sg_dma_address(s) = sg_phys(s);
if (dma_map_sg(drm->dev, sgt->sgl, sgt->nents, DMA_TO_DEVICE) == 0) { if (dma_map_sg(drm->dev, sgt->sgl, sgt->nents, DMA_TO_DEVICE) == 0)
sgt = ERR_PTR(-ENOMEM);
goto release_sgt; goto release_sgt;
}
bo->sgt = sgt; bo->sgt = sgt;
...@@ -256,6 +224,7 @@ static int tegra_bo_get_pages(struct drm_device *drm, struct tegra_bo *bo) ...@@ -256,6 +224,7 @@ static int tegra_bo_get_pages(struct drm_device *drm, struct tegra_bo *bo)
release_sgt: release_sgt:
sg_free_table(sgt); sg_free_table(sgt);
kfree(sgt); kfree(sgt);
sgt = ERR_PTR(-ENOMEM);
put_pages: put_pages:
drm_gem_put_pages(&bo->gem, bo->pages, false, false); drm_gem_put_pages(&bo->gem, bo->pages, false, false);
return PTR_ERR(sgt); return PTR_ERR(sgt);
......
This diff is collapsed.
...@@ -12,9 +12,9 @@ ...@@ -12,9 +12,9 @@
#include "mipi-phy.h" #include "mipi-phy.h"
/* /*
* Default D-PHY timings based on MIPI D-PHY specification. Derived from * Default D-PHY timings based on MIPI D-PHY specification. Derived from the
* the valid ranges specified in Section 5.9 of the D-PHY specification * valid ranges specified in Section 6.9, Table 14, Page 40 of the D-PHY
* with minor adjustments. * specification (v1.2) with minor adjustments.
*/ */
int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing, int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing,
unsigned long period) unsigned long period)
...@@ -34,7 +34,20 @@ int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing, ...@@ -34,7 +34,20 @@ int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing,
timing->hszero = 145 + 5 * period; timing->hszero = 145 + 5 * period;
timing->hssettle = 85 + 6 * period; timing->hssettle = 85 + 6 * period;
timing->hsskip = 40; 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->init = 100000;
timing->lpx = 60; timing->lpx = 60;
timing->taget = 5 * timing->lpx; timing->taget = 5 * timing->lpx;
...@@ -46,8 +59,8 @@ int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing, ...@@ -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, * Validate D-PHY timing according to MIPI D-PHY specification (v1.2, Section
* Section 5.9 "Global Operation Timing Parameters". * Section 6.9 "Global Operation Timing Parameters").
*/ */
int mipi_dphy_timing_validate(struct mipi_dphy_timing *timing, int mipi_dphy_timing_validate(struct mipi_dphy_timing *timing,
unsigned long period) unsigned long period)
......
...@@ -9,10 +9,11 @@ ...@@ -9,10 +9,11 @@
#include <linux/of_gpio.h> #include <linux/of_gpio.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_panel.h> #include <drm/drm_panel.h>
#include "drm.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 tegra_output *output = connector_to_output(connector);
struct edid *edid = NULL; struct edid *edid = NULL;
...@@ -43,43 +44,20 @@ static int tegra_connector_get_modes(struct drm_connector *connector) ...@@ -43,43 +44,20 @@ static int tegra_connector_get_modes(struct drm_connector *connector)
return err; return err;
} }
static int tegra_connector_mode_valid(struct drm_connector *connector, struct drm_encoder *
struct drm_display_mode *mode) tegra_output_connector_best_encoder(struct drm_connector *connector)
{
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 tegra_output *output = connector_to_output(connector); struct tegra_output *output = connector_to_output(connector);
return &output->encoder; return &output->encoder;
} }
static const struct drm_connector_helper_funcs connector_helper_funcs = { enum drm_connector_status
.get_modes = tegra_connector_get_modes, tegra_output_connector_detect(struct drm_connector *connector, bool force)
.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)
{ {
struct tegra_output *output = connector_to_output(connector); struct tegra_output *output = connector_to_output(connector);
enum drm_connector_status status = connector_status_unknown; 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_is_valid(output->hpd_gpio)) {
if (gpio_get_value(output->hpd_gpio) == 0) if (gpio_get_value(output->hpd_gpio) == 0)
status = connector_status_disconnected; status = connector_status_disconnected;
...@@ -90,95 +68,22 @@ tegra_connector_detect(struct drm_connector *connector, bool force) ...@@ -90,95 +68,22 @@ tegra_connector_detect(struct drm_connector *connector, bool force)
status = connector_status_disconnected; status = connector_status_disconnected;
else else
status = connector_status_connected; status = connector_status_connected;
if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS)
status = connector_status_connected;
} }
return status; return status;
} }
static void drm_connector_clear(struct drm_connector *connector) void tegra_output_connector_destroy(struct drm_connector *connector)
{
memset(connector, 0, sizeof(*connector));
}
static void tegra_connector_destroy(struct drm_connector *connector)
{ {
drm_connector_unregister(connector); drm_connector_unregister(connector);
drm_connector_cleanup(connector); drm_connector_cleanup(connector);
drm_connector_clear(connector);
} }
static const struct drm_connector_funcs connector_funcs = { void tegra_output_encoder_destroy(struct drm_encoder *encoder)
.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)
{ {
drm_encoder_cleanup(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) static irqreturn_t hpd_irq(int irq, void *data)
{ {
struct tegra_output *output = data; struct tegra_output *output = data;
...@@ -268,7 +173,7 @@ int tegra_output_probe(struct tegra_output *output) ...@@ -268,7 +173,7 @@ int tegra_output_probe(struct tegra_output *output)
return 0; 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)) { if (gpio_is_valid(output->hpd_gpio)) {
free_irq(output->hpd_irq, output); free_irq(output->hpd_irq, output);
...@@ -277,56 +182,17 @@ int tegra_output_remove(struct tegra_output *output) ...@@ -277,56 +182,17 @@ int tegra_output_remove(struct tegra_output *output)
if (output->ddc) if (output->ddc)
put_device(&output->ddc->dev); put_device(&output->ddc->dev);
return 0;
} }
int tegra_output_init(struct drm_device *drm, struct tegra_output *output) 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;
}
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; if (output->panel) {
err = drm_panel_attach(output->panel, &output->connector);
if (err < 0)
return err;
}
/* /*
* The connector is now registered and ready to receive hotplug events * The connector is now registered and ready to receive hotplug events
...@@ -338,7 +204,7 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output) ...@@ -338,7 +204,7 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output)
return 0; 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 * The connector is going away, so the interrupt must be disabled to
...@@ -349,6 +215,4 @@ int tegra_output_exit(struct tegra_output *output) ...@@ -349,6 +215,4 @@ int tegra_output_exit(struct tegra_output *output)
if (output->panel) if (output->panel)
drm_panel_detach(output->panel); drm_panel_detach(output->panel);
return 0;
} }
...@@ -9,6 +9,9 @@ ...@@ -9,6 +9,9 @@
#include <linux/clk.h> #include <linux/clk.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_panel.h>
#include "drm.h" #include "drm.h"
#include "dc.h" #include "dc.h"
...@@ -85,13 +88,65 @@ static void tegra_dc_write_regs(struct tegra_dc *dc, ...@@ -85,13 +88,65 @@ static void tegra_dc_write_regs(struct tegra_dc *dc,
tegra_dc_writel(dc, table[i].value, table[i].offset); 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)
{
}
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); struct tegra_rgb *rgb = to_rgb(output);
unsigned long value; u32 value;
if (rgb->enabled) if (output->panel)
return 0; drm_panel_prepare(output->panel);
tegra_dc_write_regs(rgb->dc, rgb_enable, ARRAY_SIZE(rgb_enable)); 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) ...@@ -113,64 +168,39 @@ static int tegra_output_rgb_enable(struct tegra_output *output)
value = SC0_H_QUALIFIER_NONE | SC1_H_QUALIFIER_NONE; value = SC0_H_QUALIFIER_NONE | SC1_H_QUALIFIER_NONE;
tegra_dc_writel(rgb->dc, value, DC_DISP_SHIFT_CLOCK_OPTIONS); tegra_dc_writel(rgb->dc, value, DC_DISP_SHIFT_CLOCK_OPTIONS);
value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_COMMAND); tegra_dc_commit(rgb->dc);
value &= ~DISP_CTRL_MODE_MASK;
value |= DISP_CTRL_MODE_C_DISPLAY;
tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_COMMAND);
value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_POWER_CONTROL); if (output->panel)
value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | drm_panel_enable(output->panel);
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;
} }
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); 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); if (output->panel)
tegra_dc_writel(rgb->dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); drm_panel_disable(output->panel);
tegra_dc_write_regs(rgb->dc, rgb_disable, ARRAY_SIZE(rgb_disable)); tegra_dc_write_regs(rgb->dc, rgb_disable, ARRAY_SIZE(rgb_disable));
tegra_dc_commit(rgb->dc);
rgb->enabled = false; if (output->panel)
drm_panel_unprepare(output->panel);
return 0;
} }
static int tegra_output_rgb_setup_clock(struct tegra_output *output, static int
struct clk *clk, unsigned long pclk, tegra_rgb_encoder_atomic_check(struct drm_encoder *encoder,
unsigned int *div) 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); struct tegra_rgb *rgb = to_rgb(output);
unsigned int div;
int err; 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 * 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 * 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, ...@@ -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 * and hope that the desired frequency can be matched (or at least
* matched sufficiently close that the panel will still work). * 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);
return 0; if (err < 0) {
} dev_err(output->dev, "failed to setup CRTC state: %d\n", err);
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; return err;
} }
static const struct tegra_output_ops rgb_ops = { static const struct drm_encoder_helper_funcs tegra_rgb_encoder_helper_funcs = {
.enable = tegra_output_rgb_enable, .dpms = tegra_rgb_encoder_dpms,
.disable = tegra_output_rgb_disable, .prepare = tegra_rgb_encoder_prepare,
.setup_clock = tegra_output_rgb_setup_clock, .commit = tegra_rgb_encoder_commit,
.check_mode = tegra_output_rgb_check_mode, .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) int tegra_dc_rgb_probe(struct tegra_dc *dc)
...@@ -262,64 +286,58 @@ 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 tegra_dc_rgb_remove(struct tegra_dc *dc)
{ {
int err;
if (!dc->rgb) if (!dc->rgb)
return 0; return 0;
err = tegra_output_remove(dc->rgb); tegra_output_remove(dc->rgb);
if (err < 0) dc->rgb = NULL;
return err;
return 0; return 0;
} }
int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc) 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; int err;
if (!dc->rgb) if (!dc->rgb)
return -ENODEV; return -ENODEV;
rgb->output.type = TEGRA_OUTPUT_RGB; drm_connector_init(drm, &output->connector, &tegra_rgb_connector_funcs,
rgb->output.ops = &rgb_ops; DRM_MODE_CONNECTOR_LVDS);
drm_connector_helper_add(&output->connector,
&tegra_rgb_connector_helper_funcs);
output->connector.dpms = DRM_MODE_DPMS_OFF;
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);
err = tegra_output_init(dc->base.dev, &rgb->output); drm_mode_connector_attach_encoder(&output->connector,
&output->encoder);
drm_connector_register(&output->connector);
err = tegra_output_init(drm, output);
if (err < 0) { 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; return err;
} }
/* /*
* By default, outputs can be associated with each display controller. * Other outputs can be attached to either display controller. The RGB
* RGB outputs are an exception, so we make sure they can be attached * outputs are an exception and work only with their parent display
* to only their parent display controller. * controller.
*/ */
rgb->output.encoder.possible_crtcs = drm_crtc_mask(&dc->base); output->encoder.possible_crtcs = drm_crtc_mask(&dc->base);
return 0; return 0;
} }
int tegra_dc_rgb_exit(struct tegra_dc *dc) int tegra_dc_rgb_exit(struct tegra_dc *dc)
{ {
if (dc->rgb) { if (dc->rgb)
int err; tegra_output_exit(dc->rgb);
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;
}
return 0; return 0;
} }
This diff is collapsed.
...@@ -72,13 +72,14 @@ static void host1x_subdev_del(struct host1x_subdev *subdev) ...@@ -72,13 +72,14 @@ static void host1x_subdev_del(struct host1x_subdev *subdev)
/** /**
* host1x_device_parse_dt() - scan device tree and add matching subdevices * 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; struct device_node *np;
int err; int err;
for_each_child_of_node(device->dev.parent->of_node, np) { 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)) { of_device_is_available(np)) {
err = host1x_subdev_add(device, np); err = host1x_subdev_add(device, np);
if (err < 0) if (err < 0)
...@@ -109,14 +110,12 @@ static void host1x_subdev_register(struct host1x_device *device, ...@@ -109,14 +110,12 @@ static void host1x_subdev_register(struct host1x_device *device,
mutex_unlock(&device->clients_lock); mutex_unlock(&device->clients_lock);
mutex_unlock(&device->subdevs_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)) { if (list_empty(&device->subdevs)) {
err = device->driver->probe(device); err = device_add(&device->dev);
if (err < 0) 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, ...@@ -124,16 +123,16 @@ static void __host1x_subdev_unregister(struct host1x_device *device,
struct host1x_subdev *subdev) struct host1x_subdev *subdev)
{ {
struct host1x_client *client = subdev->client; struct host1x_client *client = subdev->client;
int err;
/* /*
* If all subdevices have been activated, we're about to remove the * If all subdevices have been activated, we're about to remove the
* first active subdevice, so unload the driver first. * first active subdevice, so unload the driver first.
*/ */
if (list_empty(&device->subdevs)) { if (list_empty(&device->subdevs)) {
err = device->driver->remove(device); if (device->registered) {
if (err < 0) device->registered = false;
dev_err(&device->dev, "remove failed: %d\n", err); device_del(&device->dev);
}
} }
/* /*
...@@ -260,24 +259,113 @@ static int host1x_del_client(struct host1x *host1x, ...@@ -260,24 +259,113 @@ static int host1x_del_client(struct host1x *host1x,
return -ENODEV; return -ENODEV;
} }
static struct bus_type host1x_bus_type = { static int host1x_device_match(struct device *dev, struct device_driver *drv)
.name = "host1x", {
}; return strcmp(dev_name(dev), drv->name) == 0;
}
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);
if (driver->probe)
return driver->probe(device);
return 0;
}
int host1x_bus_init(void) static int host1x_device_remove(struct device *dev)
{ {
return bus_register(&host1x_bus_type); 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);
} }
void host1x_bus_exit(void) 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)
{ {
bus_unregister(&host1x_bus_type); struct host1x_subdev *subdev, *sd;
struct host1x_client *client, *cl;
mutex_lock(&device->subdevs_lock);
/* unregister subdevices */
list_for_each_entry_safe(subdev, sd, &device->active, list) {
/*
* host1x_subdev_unregister() will remove the client from
* any lists, so we'll need to manually add it back to the
* list of idle clients.
*
* XXX: Alternatively, perhaps don't remove the client from
* any lists in host1x_subdev_unregister() and instead do
* that explicitly from host1x_unregister_client()?
*/
client = subdev->client;
__host1x_subdev_unregister(device, subdev);
/* add the client to the list of idle clients */
mutex_lock(&clients_lock);
list_add_tail(&client->list, &clients);
mutex_unlock(&clients_lock);
}
/* remove subdevices */
list_for_each_entry_safe(subdev, sd, &device->subdevs, list)
host1x_subdev_del(subdev);
mutex_unlock(&device->subdevs_lock);
/* move clients to idle list */
mutex_lock(&clients_lock);
mutex_lock(&device->clients_lock);
list_for_each_entry_safe(client, cl, &device->clients, list)
list_move_tail(&client->list, &clients);
mutex_unlock(&device->clients_lock);
mutex_unlock(&clients_lock);
/* finally remove the device */
list_del_init(&device->list);
} }
static void host1x_device_release(struct device *dev) static void host1x_device_release(struct device *dev)
{ {
struct host1x_device *device = to_host1x_device(dev); struct host1x_device *device = to_host1x_device(dev);
__host1x_device_del(device);
kfree(device); kfree(device);
} }
...@@ -293,6 +381,8 @@ static int host1x_device_add(struct host1x *host1x, ...@@ -293,6 +381,8 @@ static int host1x_device_add(struct host1x *host1x,
if (!device) if (!device)
return -ENOMEM; return -ENOMEM;
device_initialize(&device->dev);
mutex_init(&device->subdevs_lock); mutex_init(&device->subdevs_lock);
INIT_LIST_HEAD(&device->subdevs); INIT_LIST_HEAD(&device->subdevs);
INIT_LIST_HEAD(&device->active); INIT_LIST_HEAD(&device->active);
...@@ -303,24 +393,18 @@ static int host1x_device_add(struct host1x *host1x, ...@@ -303,24 +393,18 @@ static int host1x_device_add(struct host1x *host1x,
device->dev.coherent_dma_mask = host1x->dev->coherent_dma_mask; device->dev.coherent_dma_mask = host1x->dev->coherent_dma_mask;
device->dev.dma_mask = &device->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.release = host1x_device_release;
dev_set_name(&device->dev, "%s", driver->name);
device->dev.bus = &host1x_bus_type; device->dev.bus = &host1x_bus_type;
device->dev.parent = host1x->dev; device->dev.parent = host1x->dev;
err = device_register(&device->dev); err = host1x_device_parse_dt(device, driver);
if (err < 0)
return err;
err = host1x_device_parse_dt(device);
if (err < 0) { if (err < 0) {
device_unregister(&device->dev); kfree(device);
return err; return err;
} }
mutex_lock(&host1x->devices_lock);
list_add_tail(&device->list, &host1x->devices); list_add_tail(&device->list, &host1x->devices);
mutex_unlock(&host1x->devices_lock);
mutex_lock(&clients_lock); mutex_lock(&clients_lock);
...@@ -347,51 +431,12 @@ static int host1x_device_add(struct host1x *host1x, ...@@ -347,51 +431,12 @@ static int host1x_device_add(struct host1x *host1x,
static void host1x_device_del(struct host1x *host1x, static void host1x_device_del(struct host1x *host1x,
struct host1x_device *device) struct host1x_device *device)
{ {
struct host1x_subdev *subdev, *sd; if (device->registered) {
struct host1x_client *client, *cl; device->registered = false;
device_del(&device->dev);
mutex_lock(&device->subdevs_lock);
/* unregister subdevices */
list_for_each_entry_safe(subdev, sd, &device->active, list) {
/*
* host1x_subdev_unregister() will remove the client from
* any lists, so we'll need to manually add it back to the
* list of idle clients.
*
* XXX: Alternatively, perhaps don't remove the client from
* any lists in host1x_subdev_unregister() and instead do
* that explicitly from host1x_unregister_client()?
*/
client = subdev->client;
__host1x_subdev_unregister(device, subdev);
/* add the client to the list of idle clients */
mutex_lock(&clients_lock);
list_add_tail(&client->list, &clients);
mutex_unlock(&clients_lock);
} }
/* remove subdevices */ put_device(&device->dev);
list_for_each_entry_safe(subdev, sd, &device->subdevs, list)
host1x_subdev_del(subdev);
mutex_unlock(&device->subdevs_lock);
/* move clients to idle list */
mutex_lock(&clients_lock);
mutex_lock(&device->clients_lock);
list_for_each_entry_safe(client, cl, &device->clients, list)
list_move_tail(&client->list, &clients);
mutex_unlock(&device->clients_lock);
mutex_unlock(&clients_lock);
/* finally remove the device */
list_del_init(&device->list);
device_unregister(&device->dev);
} }
static void host1x_attach_driver(struct host1x *host1x, static void host1x_attach_driver(struct host1x *host1x,
...@@ -409,11 +454,11 @@ 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); err = host1x_device_add(host1x, driver);
if (err < 0) if (err < 0)
dev_err(host1x->dev, "failed to allocate device: %d\n", err); dev_err(host1x->dev, "failed to allocate device: %d\n", err);
mutex_unlock(&host1x->devices_lock);
} }
static void host1x_detach_driver(struct host1x *host1x, static void host1x_detach_driver(struct host1x *host1x,
...@@ -466,7 +511,8 @@ int host1x_unregister(struct host1x *host1x) ...@@ -466,7 +511,8 @@ int host1x_unregister(struct host1x *host1x)
return 0; 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; struct host1x *host1x;
...@@ -483,9 +529,12 @@ int host1x_driver_register(struct host1x_driver *driver) ...@@ -483,9 +529,12 @@ int host1x_driver_register(struct host1x_driver *driver)
mutex_unlock(&devices_lock); 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) void host1x_driver_unregister(struct host1x_driver *driver)
{ {
......
...@@ -18,10 +18,10 @@ ...@@ -18,10 +18,10 @@
#ifndef HOST1X_BUS_H #ifndef HOST1X_BUS_H
#define HOST1X_BUS_H #define HOST1X_BUS_H
struct bus_type;
struct host1x; struct host1x;
int host1x_bus_init(void); extern struct bus_type host1x_bus_type;
void host1x_bus_exit(void);
int host1x_register(struct host1x *host1x); int host1x_register(struct host1x *host1x);
int host1x_unregister(struct host1x *host1x); int host1x_unregister(struct host1x *host1x);
......
...@@ -216,7 +216,7 @@ static int __init tegra_host1x_init(void) ...@@ -216,7 +216,7 @@ static int __init tegra_host1x_init(void)
{ {
int err; int err;
err = host1x_bus_init(); err = bus_register(&host1x_bus_type);
if (err < 0) if (err < 0)
return err; return err;
...@@ -233,7 +233,7 @@ static int __init tegra_host1x_init(void) ...@@ -233,7 +233,7 @@ static int __init tegra_host1x_init(void)
unregister_host1x: unregister_host1x:
platform_driver_unregister(&tegra_host1x_driver); platform_driver_unregister(&tegra_host1x_driver);
unregister_bus: unregister_bus:
host1x_bus_exit(); bus_unregister(&host1x_bus_type);
return err; return err;
} }
module_init(tegra_host1x_init); module_init(tegra_host1x_init);
...@@ -242,7 +242,7 @@ static void __exit tegra_host1x_exit(void) ...@@ -242,7 +242,7 @@ static void __exit tegra_host1x_exit(void)
{ {
platform_driver_unregister(&tegra_mipi_driver); platform_driver_unregister(&tegra_mipi_driver);
platform_driver_unregister(&tegra_host1x_driver); platform_driver_unregister(&tegra_host1x_driver);
host1x_bus_exit(); bus_unregister(&host1x_bus_type);
} }
module_exit(tegra_host1x_exit); module_exit(tegra_host1x_exit);
......
...@@ -127,4 +127,41 @@ void drm_atomic_helper_connector_destroy_state(struct drm_connector *connector, ...@@ -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) \ #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_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_ */ #endif /* DRM_ATOMIC_HELPER_H_ */
...@@ -115,6 +115,7 @@ struct drm_crtc_helper_funcs { ...@@ -115,6 +115,7 @@ struct drm_crtc_helper_funcs {
* @get_crtc: return CRTC that the encoder is currently attached to * @get_crtc: return CRTC that the encoder is currently attached to
* @detect: connection status detection * @detect: connection status detection
* @disable: disable encoder when not in use (overrides DPMS off) * @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. * The helper operations are called by the mid-layer CRTC helper.
*/ */
...@@ -137,6 +138,11 @@ struct drm_encoder_helper_funcs { ...@@ -137,6 +138,11 @@ struct drm_encoder_helper_funcs {
struct drm_connector *connector); struct drm_connector *connector);
/* disable encoder when not in use - more explicit than dpms off */ /* disable encoder when not in use - more explicit than dpms off */
void (*disable)(struct drm_encoder *encoder); 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, ...@@ -52,7 +52,8 @@ extern int drm_crtc_init(struct drm_device *dev,
* @prepare_fb: prepare a framebuffer for use by the plane * @prepare_fb: prepare a framebuffer for use by the plane
* @cleanup_fb: cleanup a framebuffer when it's no longer used 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_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. * The helper operations are called by the mid-layer CRTC helper.
*/ */
...@@ -66,6 +67,8 @@ struct drm_plane_helper_funcs { ...@@ -66,6 +67,8 @@ struct drm_plane_helper_funcs {
struct drm_plane_state *state); struct drm_plane_state *state);
void (*atomic_update)(struct drm_plane *plane, void (*atomic_update)(struct drm_plane *plane,
struct drm_plane_state *old_state); 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, static inline void drm_plane_helper_add(struct drm_plane *plane,
......
...@@ -250,17 +250,29 @@ void host1x_job_unpin(struct host1x_job *job); ...@@ -250,17 +250,29 @@ void host1x_job_unpin(struct host1x_job *job);
struct host1x_device; struct host1x_device;
struct host1x_driver { struct host1x_driver {
struct device_driver driver;
const struct of_device_id *subdevs; const struct of_device_id *subdevs;
struct list_head list; struct list_head list;
const char *name;
int (*probe)(struct host1x_device *device); int (*probe)(struct host1x_device *device);
int (*remove)(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); 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_device {
struct host1x_driver *driver; struct host1x_driver *driver;
struct list_head list; struct list_head list;
...@@ -272,6 +284,8 @@ struct host1x_device { ...@@ -272,6 +284,8 @@ struct host1x_device {
struct mutex clients_lock; struct mutex clients_lock;
struct list_head clients; struct list_head clients;
bool registered;
}; };
static inline struct host1x_device *to_host1x_device(struct device *dev) static inline struct host1x_device *to_host1x_device(struct device *dev)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment