Commit 69405d3d authored by Dave Airlie's avatar Dave Airlie

Merge tag 'topic/drm-misc-2016-10-11' of git://anongit.freedesktop.org/drm-intel into drm-next

Just flushing out my -misc queue. Slightly important are the prime
refcount/unload fixes from Chris.

There's also the reservation stuff from Chris still pending, and Sumits
hasn't landed that yet. Might get another pull for that, but pls don't
hold up the main pull for it ;-)

* tag 'topic/drm-misc-2016-10-11' of git://anongit.freedesktop.org/drm-intel:
  drm/crtc: constify drm_crtc_index parameter
  drm: use the right function name in documentation
  drm: Release resources with a safer function
  drm: Fix up kerneldoc for new drm_gem_dmabuf_export()
  drm/bridge: Drop drm_connector_unregister and call drm_connector_cleanup directly
  drm/fb-helper: fix sphinx markup for DRM_FB_HELPER_DEFAULT_OPS
  drm/bridge: Add RGB to VGA bridge support
  drm/prime: Take a ref on the drm_dev when exporting a dma_buf
  drm/prime: Pass the right module owner through to dma_buf_export()
  drm/bridge: Call drm_connector_cleanup directly
  drm: simple_kms_helper: Add prepare_fb and cleanup_fb hooks
  drm: Release resources with a safer function
parents 28da9ed6 a5bd451b
Dumb RGB to VGA DAC bridge
---------------------------
This binding is aimed for dumb RGB to VGA DAC based bridges that do not require
any configuration.
Required properties:
- compatible: Must be "dumb-vga-dac"
Required nodes:
This device has two video ports. Their connections are modelled using the OF
graph bindings specified in Documentation/devicetree/bindings/graph.txt.
- Video port 0 for RGB input
- Video port 1 for VGA output
Example
-------
bridge {
compatible = "dumb-vga-dac";
#address-cells = <1>;
#size-cells = <0>;
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
vga_bridge_in: endpoint {
remote-endpoint = <&tcon0_out_vga>;
};
};
port@1 {
reg = <1>;
vga_bridge_out: endpoint {
remote-endpoint = <&vga_con_in>;
};
};
};
};
......@@ -547,7 +547,7 @@ armada_gem_prime_export(struct drm_device *dev, struct drm_gem_object *obj,
exp_info.flags = O_RDWR;
exp_info.priv = obj;
return dma_buf_export(&exp_info);
return drm_gem_dmabuf_export(dev, &exp_info);
}
struct drm_gem_object *
......
......@@ -17,6 +17,13 @@ config DRM_ANALOGIX_ANX78XX
the HDMI output of an application processor to MyDP
or DisplayPort.
config DRM_DUMB_VGA_DAC
tristate "Dumb VGA DAC Bridge support"
depends on OF
select DRM_KMS_HELPER
help
Support for RGB to VGA DAC based bridges
config DRM_DW_HDMI
tristate
select DRM_KMS_HELPER
......
ccflags-y := -Iinclude/drm
obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
obj-$(CONFIG_DRM_DUMB_VGA_DAC) += dumb-vga-dac.o
obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
......
......@@ -1001,16 +1001,11 @@ static enum drm_connector_status anx78xx_detect(struct drm_connector *connector,
return connector_status_connected;
}
static void anx78xx_connector_destroy(struct drm_connector *connector)
{
drm_connector_cleanup(connector);
}
static const struct drm_connector_funcs anx78xx_connector_funcs = {
.dpms = drm_atomic_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = anx78xx_detect,
.destroy = anx78xx_connector_destroy,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
......
......@@ -999,18 +999,11 @@ analogix_dp_detect(struct drm_connector *connector, bool force)
return status;
}
static void analogix_dp_connector_destroy(struct drm_connector *connector)
{
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
}
static const struct drm_connector_funcs analogix_dp_connector_funcs = {
.dpms = drm_atomic_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = analogix_dp_detect,
.destroy = analogix_dp_connector_destroy,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
......
/*
* Copyright (C) 2015-2016 Free Electrons
* Copyright (C) 2015-2016 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/of_graph.h>
#include <drm/drmP.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
struct dumb_vga {
struct drm_bridge bridge;
struct drm_connector connector;
struct i2c_adapter *ddc;
};
static inline struct dumb_vga *
drm_bridge_to_dumb_vga(struct drm_bridge *bridge)
{
return container_of(bridge, struct dumb_vga, bridge);
}
static inline struct dumb_vga *
drm_connector_to_dumb_vga(struct drm_connector *connector)
{
return container_of(connector, struct dumb_vga, connector);
}
static int dumb_vga_get_modes(struct drm_connector *connector)
{
struct dumb_vga *vga = drm_connector_to_dumb_vga(connector);
struct edid *edid;
int ret;
if (IS_ERR(vga->ddc))
goto fallback;
edid = drm_get_edid(connector, vga->ddc);
if (!edid) {
DRM_INFO("EDID readout failed, falling back to standard modes\n");
goto fallback;
}
drm_mode_connector_update_edid_property(connector, edid);
return drm_add_edid_modes(connector, edid);
fallback:
/*
* In case we cannot retrieve the EDIDs (broken or missing i2c
* bus), fallback on the XGA standards
*/
ret = drm_add_modes_noedid(connector, 1920, 1200);
/* And prefer a mode pretty much anyone can handle */
drm_set_preferred_mode(connector, 1024, 768);
return ret;
}
static const struct drm_connector_helper_funcs dumb_vga_con_helper_funcs = {
.get_modes = dumb_vga_get_modes,
};
static enum drm_connector_status
dumb_vga_connector_detect(struct drm_connector *connector, bool force)
{
struct dumb_vga *vga = drm_connector_to_dumb_vga(connector);
/*
* Even if we have an I2C bus, we can't assume that the cable
* is disconnected if drm_probe_ddc fails. Some cables don't
* wire the DDC pins, or the I2C bus might not be working at
* all.
*/
if (!IS_ERR(vga->ddc) && drm_probe_ddc(vga->ddc))
return connector_status_connected;
return connector_status_unknown;
}
static const struct drm_connector_funcs dumb_vga_con_funcs = {
.dpms = drm_atomic_helper_connector_dpms,
.detect = dumb_vga_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static int dumb_vga_attach(struct drm_bridge *bridge)
{
struct dumb_vga *vga = drm_bridge_to_dumb_vga(bridge);
int ret;
if (!bridge->encoder) {
DRM_ERROR("Missing encoder\n");
return -ENODEV;
}
drm_connector_helper_add(&vga->connector,
&dumb_vga_con_helper_funcs);
ret = drm_connector_init(bridge->dev, &vga->connector,
&dumb_vga_con_funcs, DRM_MODE_CONNECTOR_VGA);
if (ret) {
DRM_ERROR("Failed to initialize connector\n");
return ret;
}
drm_mode_connector_attach_encoder(&vga->connector,
bridge->encoder);
return 0;
}
static const struct drm_bridge_funcs dumb_vga_bridge_funcs = {
.attach = dumb_vga_attach,
};
static struct i2c_adapter *dumb_vga_retrieve_ddc(struct device *dev)
{
struct device_node *end_node, *phandle, *remote;
struct i2c_adapter *ddc;
end_node = of_graph_get_endpoint_by_regs(dev->of_node, 1, -1);
if (!end_node) {
dev_err(dev, "Missing connector endpoint\n");
return ERR_PTR(-ENODEV);
}
remote = of_graph_get_remote_port_parent(end_node);
of_node_put(end_node);
if (!remote) {
dev_err(dev, "Enable to parse remote node\n");
return ERR_PTR(-EINVAL);
}
phandle = of_parse_phandle(remote, "ddc-i2c-bus", 0);
of_node_put(remote);
if (!phandle)
return ERR_PTR(-ENODEV);
ddc = of_get_i2c_adapter_by_node(phandle);
of_node_put(phandle);
if (!ddc)
return ERR_PTR(-EPROBE_DEFER);
return ddc;
}
static int dumb_vga_probe(struct platform_device *pdev)
{
struct dumb_vga *vga;
int ret;
vga = devm_kzalloc(&pdev->dev, sizeof(*vga), GFP_KERNEL);
if (!vga)
return -ENOMEM;
platform_set_drvdata(pdev, vga);
vga->ddc = dumb_vga_retrieve_ddc(&pdev->dev);
if (IS_ERR(vga->ddc)) {
if (PTR_ERR(vga->ddc) == -ENODEV) {
dev_dbg(&pdev->dev,
"No i2c bus specified. Disabling EDID readout\n");
} else {
dev_err(&pdev->dev, "Couldn't retrieve i2c bus\n");
return PTR_ERR(vga->ddc);
}
}
vga->bridge.funcs = &dumb_vga_bridge_funcs;
vga->bridge.of_node = pdev->dev.of_node;
ret = drm_bridge_add(&vga->bridge);
if (ret && !IS_ERR(vga->ddc))
i2c_put_adapter(vga->ddc);
return ret;
}
static int dumb_vga_remove(struct platform_device *pdev)
{
struct dumb_vga *vga = platform_get_drvdata(pdev);
drm_bridge_remove(&vga->bridge);
if (!IS_ERR(vga->ddc))
i2c_put_adapter(vga->ddc);
return 0;
}
static const struct of_device_id dumb_vga_match[] = {
{ .compatible = "dumb-vga-dac" },
{},
};
MODULE_DEVICE_TABLE(of, dumb_vga_match);
static struct platform_driver dumb_vga_driver = {
.probe = dumb_vga_probe,
.remove = dumb_vga_remove,
.driver = {
.name = "dumb-vga-dac",
.of_match_table = dumb_vga_match,
},
};
module_platform_driver(dumb_vga_driver);
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
MODULE_DESCRIPTION("Dumb VGA DAC bridge driver");
MODULE_LICENSE("GPL");
......@@ -1477,12 +1477,6 @@ dw_hdmi_connector_mode_valid(struct drm_connector *connector,
return mode_status;
}
static void dw_hdmi_connector_destroy(struct drm_connector *connector)
{
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
}
static void dw_hdmi_connector_force(struct drm_connector *connector)
{
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
......@@ -1499,7 +1493,7 @@ static const struct drm_connector_funcs dw_hdmi_connector_funcs = {
.dpms = drm_atomic_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = dw_hdmi_connector_detect,
.destroy = dw_hdmi_connector_destroy,
.destroy = drm_connector_cleanup,
.force = dw_hdmi_connector_force,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
......
......@@ -245,16 +245,11 @@ static enum drm_connector_status ptn3460_detect(struct drm_connector *connector,
return connector_status_connected;
}
static void ptn3460_connector_destroy(struct drm_connector *connector)
{
drm_connector_cleanup(connector);
}
static const struct drm_connector_funcs ptn3460_connector_funcs = {
.dpms = drm_atomic_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = ptn3460_detect,
.destroy = ptn3460_connector_destroy,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
......
......@@ -483,16 +483,11 @@ static enum drm_connector_status ps8622_detect(struct drm_connector *connector,
return connector_status_connected;
}
static void ps8622_connector_destroy(struct drm_connector *connector)
{
drm_connector_cleanup(connector);
}
static const struct drm_connector_funcs ps8622_connector_funcs = {
.dpms = drm_atomic_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = ps8622_detect,
.destroy = ps8622_connector_destroy,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
......
......@@ -1165,17 +1165,11 @@ static const struct drm_connector_helper_funcs tc_connector_helper_funcs = {
.best_encoder = tc_connector_best_encoder,
};
static void tc_connector_destroy(struct drm_connector *connector)
{
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
}
static const struct drm_connector_funcs tc_connector_funcs = {
.dpms = drm_atomic_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = tc_connector_detect,
.destroy = tc_connector_destroy,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
......
......@@ -250,10 +250,10 @@ int drm_connector_init(struct drm_device *dev,
connector->debugfs_entry = NULL;
out_put_type_id:
if (ret)
ida_remove(connector_ida, connector->connector_type_id);
ida_simple_remove(connector_ida, connector->connector_type_id);
out_put_id:
if (ret)
ida_remove(&config->connector_ida, connector->index);
ida_simple_remove(&config->connector_ida, connector->index);
out_put:
if (ret)
drm_mode_object_unregister(dev, &connector->base);
......@@ -341,10 +341,10 @@ void drm_connector_cleanup(struct drm_connector *connector)
list_for_each_entry_safe(mode, t, &connector->modes, head)
drm_mode_remove(connector, mode);
ida_remove(&drm_connector_enum_list[connector->connector_type].ida,
ida_simple_remove(&drm_connector_enum_list[connector->connector_type].ida,
connector->connector_type_id);
ida_remove(&dev->mode_config.connector_ida,
ida_simple_remove(&dev->mode_config.connector_ida,
connector->index);
kfree(connector->display_info.bus_formats);
......
......@@ -283,19 +283,48 @@ static void drm_gem_unmap_dma_buf(struct dma_buf_attachment *attach,
/* nothing to be done here */
}
/**
* drm_gem_dmabuf_export - dma_buf export implementation for GEM
* @dev: parent device for the exported dmabuf
* @exp_info: the export information used by dma_buf_export()
*
* This wraps dma_buf_export() for use by generic GEM drivers that are using
* drm_gem_dmabuf_release(). In addition to calling dma_buf_export(), we take
* a reference to the drm_device which is released by drm_gem_dmabuf_release().
*
* Returns the new dmabuf.
*/
struct dma_buf *drm_gem_dmabuf_export(struct drm_device *dev,
struct dma_buf_export_info *exp_info)
{
struct dma_buf *dma_buf;
dma_buf = dma_buf_export(exp_info);
if (!IS_ERR(dma_buf))
drm_dev_ref(dev);
return dma_buf;
}
EXPORT_SYMBOL(drm_gem_dmabuf_export);
/**
* drm_gem_dmabuf_release - dma_buf release implementation for GEM
* @dma_buf: buffer to be released
*
* Generic release function for dma_bufs exported as PRIME buffers. GEM drivers
* must use this in their dma_buf ops structure as the release callback.
* drm_gem_dmabuf_release() should be used in conjunction with
* drm_gem_dmabuf_export().
*/
void drm_gem_dmabuf_release(struct dma_buf *dma_buf)
{
struct drm_gem_object *obj = dma_buf->priv;
struct drm_device *dev = obj->dev;
/* drop the reference on the export fd holds */
drm_gem_object_unreference_unlocked(obj);
drm_dev_unref(dev);
}
EXPORT_SYMBOL(drm_gem_dmabuf_release);
......@@ -397,19 +426,22 @@ static const struct dma_buf_ops drm_gem_prime_dmabuf_ops = {
* using the PRIME helpers.
*/
struct dma_buf *drm_gem_prime_export(struct drm_device *dev,
struct drm_gem_object *obj, int flags)
struct drm_gem_object *obj,
int flags)
{
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
exp_info.ops = &drm_gem_prime_dmabuf_ops;
exp_info.size = obj->size;
exp_info.flags = flags;
exp_info.priv = obj;
struct dma_buf_export_info exp_info = {
.exp_name = KBUILD_MODNAME, /* white lie for debug */
.owner = dev->driver->fops->owner,
.ops = &drm_gem_prime_dmabuf_ops,
.size = obj->size,
.flags = flags,
.priv = obj,
};
if (dev->driver->gem_prime_res_obj)
exp_info.resv = dev->driver->gem_prime_res_obj(obj);
return dma_buf_export(&exp_info);
return drm_gem_dmabuf_export(dev, &exp_info);
}
EXPORT_SYMBOL(drm_gem_prime_export);
......
......@@ -125,7 +125,33 @@ static void drm_simple_kms_plane_atomic_update(struct drm_plane *plane,
pipe->funcs->update(pipe, pstate);
}
static int drm_simple_kms_plane_prepare_fb(struct drm_plane *plane,
struct drm_plane_state *state)
{
struct drm_simple_display_pipe *pipe;
pipe = container_of(plane, struct drm_simple_display_pipe, plane);
if (!pipe->funcs || !pipe->funcs->prepare_fb)
return 0;
return pipe->funcs->prepare_fb(pipe, state);
}
static void drm_simple_kms_plane_cleanup_fb(struct drm_plane *plane,
struct drm_plane_state *state)
{
struct drm_simple_display_pipe *pipe;
pipe = container_of(plane, struct drm_simple_display_pipe, plane);
if (!pipe->funcs || !pipe->funcs->cleanup_fb)
return;
pipe->funcs->cleanup_fb(pipe, state);
}
static const struct drm_plane_helper_funcs drm_simple_kms_plane_helper_funcs = {
.prepare_fb = drm_simple_kms_plane_prepare_fb,
.cleanup_fb = drm_simple_kms_plane_cleanup_fb,
.atomic_check = drm_simple_kms_plane_atomic_check,
.atomic_update = drm_simple_kms_plane_atomic_update,
};
......
......@@ -283,7 +283,7 @@ struct dma_buf *i915_gem_prime_export(struct drm_device *dev,
return ERR_PTR(ret);
}
dma_buf = dma_buf_export(&exp_info);
dma_buf = drm_gem_dmabuf_export(dev, &exp_info);
if (IS_ERR(dma_buf))
return dma_buf;
......
......@@ -613,7 +613,7 @@ struct dma_buf *tegra_gem_prime_export(struct drm_device *drm,
exp_info.flags = flags;
exp_info.priv = gem;
return dma_buf_export(&exp_info);
return drm_gem_dmabuf_export(drm, &exp_info);
}
struct drm_gem_object *tegra_gem_prime_import(struct drm_device *drm,
......
......@@ -209,7 +209,7 @@ struct dma_buf *udl_gem_prime_export(struct drm_device *dev,
exp_info.flags = flags;
exp_info.priv = obj;
return dma_buf_export(&exp_info);
return drm_gem_dmabuf_export(dev, &exp_info);
}
static int udl_prime_create(struct drm_device *dev,
......
......@@ -1011,8 +1011,11 @@ static inline int drm_debugfs_remove_files(const struct drm_info_list *files,
}
#endif
struct dma_buf_export_info;
extern struct dma_buf *drm_gem_prime_export(struct drm_device *dev,
struct drm_gem_object *obj, int flags);
struct drm_gem_object *obj,
int flags);
extern int drm_gem_prime_handle_to_fd(struct drm_device *dev,
struct drm_file *file_priv, uint32_t handle, uint32_t flags,
int *prime_fd);
......@@ -1020,6 +1023,8 @@ extern struct drm_gem_object *drm_gem_prime_import(struct drm_device *dev,
struct dma_buf *dma_buf);
extern int drm_gem_prime_fd_to_handle(struct drm_device *dev,
struct drm_file *file_priv, int prime_fd, uint32_t *handle);
struct dma_buf *drm_gem_dmabuf_export(struct drm_device *dev,
struct dma_buf_export_info *exp_info);
extern void drm_gem_dmabuf_release(struct dma_buf *dma_buf);
extern int drm_prime_sg_to_page_addr_arrays(struct sg_table *sgt, struct page **pages,
......
......@@ -356,7 +356,7 @@ struct drm_connector_funcs {
*
* This optional hook should be used to unregister the additional
* userspace interfaces attached to the connector from
* late_unregister(). It is called from drm_connector_unregister(),
* late_register(). It is called from drm_connector_unregister(),
* early in the driver unload sequence to disable userspace access
* before data structures are torndown.
*/
......@@ -376,7 +376,7 @@ struct drm_connector_funcs {
* @atomic_duplicate_state:
*
* Duplicate the current atomic state for this connector and return it.
* The core and helpers gurantee that any atomic state duplicated with
* The core and helpers guarantee that any atomic state duplicated with
* this hook and still owned by the caller (i.e. not transferred to the
* driver by calling ->atomic_commit() from struct
* &drm_mode_config_funcs) will be cleaned up by calling the
......
......@@ -1342,7 +1342,7 @@ extern void drm_crtc_cleanup(struct drm_crtc *crtc);
* Given a registered CRTC, return the index of that CRTC within a DRM
* device's list of CRTCs.
*/
static inline unsigned int drm_crtc_index(struct drm_crtc *crtc)
static inline unsigned int drm_crtc_index(const struct drm_crtc *crtc)
{
return crtc->index;
}
......
......@@ -218,7 +218,7 @@ struct drm_fb_helper {
};
/**
* @DRM_FB_HELPER_DEFAULT_OPS:
* define DRM_FB_HELPER_DEFAULT_OPS - helper define for drm drivers
*
* Helper define to register default implementations of drm_fb_helper
* functions. To be used in struct fb_ops of drm drivers.
......
......@@ -69,6 +69,26 @@ struct drm_simple_display_pipe_funcs {
*/
void (*update)(struct drm_simple_display_pipe *pipe,
struct drm_plane_state *plane_state);
/**
* @prepare_fb:
*
* Optional, called by struct &drm_plane_helper_funcs ->prepare_fb .
* Please read the documentation for the ->prepare_fb hook in
* struct &drm_plane_helper_funcs for more details.
*/
int (*prepare_fb)(struct drm_simple_display_pipe *pipe,
struct drm_plane_state *plane_state);
/**
* @cleanup_fb:
*
* Optional, called by struct &drm_plane_helper_funcs ->cleanup_fb .
* Please read the documentation for the ->cleanup_fb hook in
* struct &drm_plane_helper_funcs for more details.
*/
void (*cleanup_fb)(struct drm_simple_display_pipe *pipe,
struct drm_plane_state *plane_state);
};
/**
......
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