Commit d79dc0a9 authored by Dave Airlie's avatar Dave Airlie

Merge tag 'topic/drm-misc-2015-03-18' of git://anongit.freedesktop.org/drm-intel into drm-next

Another drm-misch pull request. Mostly the fbdev sizes deconfusion series
from Rob, everything else is small stuff all over. And the large i2c over
aux transfers patch, too.

* tag 'topic/drm-misc-2015-03-18' of git://anongit.freedesktop.org/drm-intel:
  drm: check that planes types are correct while initializing CRTC
  drm: change connector to tmp_connector
  drm: Fix some typo mistake of the annotations
  drm: Silence sparse warnings
  drm: %pF is only for function pointers
  drm/fb: handle tiled connectors better
  drm/fb: small cleanup
  drm/rockchip: use correct fb width/height
  drm/exynos: use correct fb width/height
  drm/cma: use correct fb width/height
  drm/atomic: minor kerneldoc typo fix
  drm/fb: document drm_fb_helper_surface_size
  drm/dp: Use large transactions for I2C over AUX
  drm/plane-helper: Fixup mismerge
  drm/atomic: Constify a bunch of functions pointer structs
parents 03be7005 522cf91f
......@@ -151,7 +151,7 @@ steal_encoder(struct drm_atomic_state *state,
static int
update_connector_routing(struct drm_atomic_state *state, int conn_idx)
{
struct drm_connector_helper_funcs *funcs;
const struct drm_connector_helper_funcs *funcs;
struct drm_encoder *new_encoder;
struct drm_crtc *encoder_crtc;
struct drm_connector *connector;
......@@ -264,7 +264,7 @@ mode_fixup(struct drm_atomic_state *state)
}
for (i = 0; i < state->num_connector; i++) {
struct drm_encoder_helper_funcs *funcs;
const struct drm_encoder_helper_funcs *funcs;
struct drm_encoder *encoder;
conn_state = state->connector_states[i];
......@@ -317,7 +317,7 @@ mode_fixup(struct drm_atomic_state *state)
}
for (i = 0; i < ncrtcs; i++) {
struct drm_crtc_helper_funcs *funcs;
const struct drm_crtc_helper_funcs *funcs;
struct drm_crtc *crtc;
crtc_state = state->crtc_states[i];
......@@ -346,7 +346,7 @@ needs_modeset(struct drm_crtc_state *state)
}
/**
* drm_atomic_helper_check - validate state object for modeset changes
* drm_atomic_helper_check_modeset - validate state object for modeset changes
* @dev: DRM device
* @state: the driver state object
*
......@@ -461,7 +461,7 @@ drm_atomic_helper_check_modeset(struct drm_device *dev,
EXPORT_SYMBOL(drm_atomic_helper_check_modeset);
/**
* drm_atomic_helper_check - validate state object for modeset changes
* drm_atomic_helper_check_planes - validate state object for planes changes
* @dev: DRM device
* @state: the driver state object
*
......@@ -481,7 +481,7 @@ drm_atomic_helper_check_planes(struct drm_device *dev,
int i, ret = 0;
for (i = 0; i < nplanes; i++) {
struct drm_plane_helper_funcs *funcs;
const struct drm_plane_helper_funcs *funcs;
struct drm_plane *plane = state->planes[i];
struct drm_plane_state *plane_state = state->plane_states[i];
......@@ -504,7 +504,7 @@ drm_atomic_helper_check_planes(struct drm_device *dev,
}
for (i = 0; i < ncrtcs; i++) {
struct drm_crtc_helper_funcs *funcs;
const struct drm_crtc_helper_funcs *funcs;
struct drm_crtc *crtc = state->crtcs[i];
if (!crtc)
......@@ -571,9 +571,9 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
int i;
for (i = 0; i < old_state->num_connector; i++) {
const struct drm_encoder_helper_funcs *funcs;
struct drm_connector_state *old_conn_state;
struct drm_connector *connector;
struct drm_encoder_helper_funcs *funcs;
struct drm_encoder *encoder;
struct drm_crtc_state *old_crtc_state;
......@@ -605,7 +605,7 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
/*
* Each encoder has at most one connector (since we always steal
* it away), so we won't call call disable hooks twice.
* it away), so we won't call disable hooks twice.
*/
if (encoder->bridge)
encoder->bridge->funcs->disable(encoder->bridge);
......@@ -623,7 +623,7 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
}
for (i = 0; i < ncrtcs; i++) {
struct drm_crtc_helper_funcs *funcs;
const struct drm_crtc_helper_funcs *funcs;
struct drm_crtc *crtc;
struct drm_crtc_state *old_crtc_state;
......@@ -713,7 +713,7 @@ crtc_set_mode(struct drm_device *dev, struct drm_atomic_state *old_state)
int i;
for (i = 0; i < ncrtcs; i++) {
struct drm_crtc_helper_funcs *funcs;
const struct drm_crtc_helper_funcs *funcs;
struct drm_crtc *crtc;
crtc = old_state->crtcs[i];
......@@ -732,9 +732,9 @@ crtc_set_mode(struct drm_device *dev, struct drm_atomic_state *old_state)
}
for (i = 0; i < old_state->num_connector; i++) {
const struct drm_encoder_helper_funcs *funcs;
struct drm_connector *connector;
struct drm_crtc_state *new_crtc_state;
struct drm_encoder_helper_funcs *funcs;
struct drm_encoder *encoder;
struct drm_display_mode *mode, *adjusted_mode;
......@@ -757,7 +757,7 @@ crtc_set_mode(struct drm_device *dev, struct drm_atomic_state *old_state)
/*
* Each encoder has at most one connector (since we always steal
* it away), so we won't call call mode_set hooks twice.
* it away), so we won't call mode_set hooks twice.
*/
if (funcs->mode_set)
funcs->mode_set(encoder, mode, adjusted_mode);
......@@ -812,7 +812,7 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
int i;
for (i = 0; i < ncrtcs; i++) {
struct drm_crtc_helper_funcs *funcs;
const struct drm_crtc_helper_funcs *funcs;
struct drm_crtc *crtc;
crtc = old_state->crtcs[i];
......@@ -838,8 +838,8 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
}
for (i = 0; i < old_state->num_connector; i++) {
const struct drm_encoder_helper_funcs *funcs;
struct drm_connector *connector;
struct drm_encoder_helper_funcs *funcs;
struct drm_encoder *encoder;
connector = old_state->connectors[i];
......@@ -858,7 +858,7 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
/*
* Each encoder has at most one connector (since we always steal
* it away), so we won't call call enable hooks twice.
* it away), so we won't call enable hooks twice.
*/
if (encoder->bridge)
encoder->bridge->funcs->pre_enable(encoder->bridge);
......@@ -1025,7 +1025,7 @@ int drm_atomic_helper_commit(struct drm_device *dev,
/*
* Everything below can be run asynchronously without the need to grab
* any modeset locks at all under one conditions: It must be guaranteed
* 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
......@@ -1114,7 +1114,7 @@ int drm_atomic_helper_prepare_planes(struct drm_device *dev,
int ret, i;
for (i = 0; i < nplanes; i++) {
struct drm_plane_helper_funcs *funcs;
const struct drm_plane_helper_funcs *funcs;
struct drm_plane *plane = state->planes[i];
struct drm_plane_state *plane_state = state->plane_states[i];
struct drm_framebuffer *fb;
......@@ -1137,7 +1137,7 @@ int drm_atomic_helper_prepare_planes(struct drm_device *dev,
fail:
for (i--; i >= 0; i--) {
struct drm_plane_helper_funcs *funcs;
const struct drm_plane_helper_funcs *funcs;
struct drm_plane *plane = state->planes[i];
struct drm_plane_state *plane_state = state->plane_states[i];
struct drm_framebuffer *fb;
......@@ -1179,7 +1179,7 @@ void drm_atomic_helper_commit_planes(struct drm_device *dev,
int i;
for (i = 0; i < ncrtcs; i++) {
struct drm_crtc_helper_funcs *funcs;
const struct drm_crtc_helper_funcs *funcs;
struct drm_crtc *crtc = old_state->crtcs[i];
if (!crtc)
......@@ -1194,7 +1194,7 @@ void drm_atomic_helper_commit_planes(struct drm_device *dev,
}
for (i = 0; i < nplanes; i++) {
struct drm_plane_helper_funcs *funcs;
const struct drm_plane_helper_funcs *funcs;
struct drm_plane *plane = old_state->planes[i];
struct drm_plane_state *old_plane_state;
......@@ -1219,7 +1219,7 @@ void drm_atomic_helper_commit_planes(struct drm_device *dev,
}
for (i = 0; i < ncrtcs; i++) {
struct drm_crtc_helper_funcs *funcs;
const struct drm_crtc_helper_funcs *funcs;
struct drm_crtc *crtc = old_state->crtcs[i];
if (!crtc)
......@@ -1254,7 +1254,7 @@ void drm_atomic_helper_cleanup_planes(struct drm_device *dev,
int i;
for (i = 0; i < nplanes; i++) {
struct drm_plane_helper_funcs *funcs;
const struct drm_plane_helper_funcs *funcs;
struct drm_plane *plane = old_state->planes[i];
struct drm_plane_state *plane_state = old_state->plane_states[i];
struct drm_framebuffer *old_fb;
......@@ -2001,10 +2001,10 @@ void drm_atomic_helper_connector_dpms(struct drm_connector *connector,
WARN_ON(!drm_modeset_is_locked(&config->connection_mutex));
list_for_each_entry(tmp_connector, &config->connector_list, head) {
if (connector->state->crtc != crtc)
if (tmp_connector->state->crtc != crtc)
continue;
if (connector->dpms == DRM_MODE_DPMS_ON) {
if (tmp_connector->dpms == DRM_MODE_DPMS_ON) {
active = true;
break;
}
......
......@@ -49,7 +49,7 @@ void drm_bridge_remove(struct drm_bridge *bridge)
}
EXPORT_SYMBOL(drm_bridge_remove);
extern int drm_bridge_attach(struct drm_device *dev, struct drm_bridge *bridge)
int drm_bridge_attach(struct drm_device *dev, struct drm_bridge *bridge)
{
if (!dev || !bridge)
return -EINVAL;
......
......@@ -659,6 +659,9 @@ int drm_crtc_init_with_planes(struct drm_device *dev, struct drm_crtc *crtc,
struct drm_mode_config *config = &dev->mode_config;
int ret;
WARN_ON(primary && primary->type != DRM_PLANE_TYPE_PRIMARY);
WARN_ON(cursor && cursor->type != DRM_PLANE_TYPE_CURSOR);
crtc->dev = dev;
crtc->funcs = funcs;
crtc->invert_dimensions = false;
......
......@@ -427,11 +427,13 @@ static u32 drm_dp_i2c_functionality(struct i2c_adapter *adapter)
* retrying the transaction as appropriate. It is assumed that the
* aux->transfer function does not modify anything in the msg other than the
* reply field.
*
* Returns bytes transferred on success, or a negative error code on failure.
*/
static int drm_dp_i2c_do_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg)
{
unsigned int retry;
int err;
int ret;
/*
* DP1.2 sections 2.7.7.1.5.6.1 and 2.7.7.1.6.6.1: A DP Source device
......@@ -440,14 +442,14 @@ static int drm_dp_i2c_do_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg)
*/
for (retry = 0; retry < 7; retry++) {
mutex_lock(&aux->hw_mutex);
err = aux->transfer(aux, msg);
ret = aux->transfer(aux, msg);
mutex_unlock(&aux->hw_mutex);
if (err < 0) {
if (err == -EBUSY)
if (ret < 0) {
if (ret == -EBUSY)
continue;
DRM_DEBUG_KMS("transaction failed: %d\n", err);
return err;
DRM_DEBUG_KMS("transaction failed: %d\n", ret);
return ret;
}
......@@ -488,9 +490,7 @@ static int drm_dp_i2c_do_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg)
* Both native ACK and I2C ACK replies received. We
* can assume the transfer was successful.
*/
if (err < msg->size)
return -EPROTO;
return 0;
return ret;
case DP_AUX_I2C_REPLY_NACK:
DRM_DEBUG_KMS("I2C nack\n");
......@@ -513,14 +513,55 @@ static int drm_dp_i2c_do_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg)
return -EREMOTEIO;
}
/*
* Keep retrying drm_dp_i2c_do_msg until all data has been transferred.
*
* Returns an error code on failure, or a recommended transfer size on success.
*/
static int drm_dp_i2c_drain_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *orig_msg)
{
int err, ret = orig_msg->size;
struct drm_dp_aux_msg msg = *orig_msg;
while (msg.size > 0) {
err = drm_dp_i2c_do_msg(aux, &msg);
if (err <= 0)
return err == 0 ? -EPROTO : err;
if (err < msg.size && err < ret) {
DRM_DEBUG_KMS("Partial I2C reply: requested %zu bytes got %d bytes\n",
msg.size, err);
ret = err;
}
msg.size -= err;
msg.buffer += err;
}
return ret;
}
/*
* Bizlink designed DP->DVI-D Dual Link adapters require the I2C over AUX
* packets to be as large as possible. If not, the I2C transactions never
* succeed. Hence the default is maximum.
*/
static int dp_aux_i2c_transfer_size __read_mostly = DP_AUX_MAX_PAYLOAD_BYTES;
module_param_unsafe(dp_aux_i2c_transfer_size, int, 0644);
MODULE_PARM_DESC(dp_aux_i2c_transfer_size,
"Number of bytes to transfer in a single I2C over DP AUX CH message, (1-16, default 16)");
static int drm_dp_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs,
int num)
{
struct drm_dp_aux *aux = adapter->algo_data;
unsigned int i, j;
unsigned transfer_size;
struct drm_dp_aux_msg msg;
int err = 0;
dp_aux_i2c_transfer_size = clamp(dp_aux_i2c_transfer_size, 1, DP_AUX_MAX_PAYLOAD_BYTES);
memset(&msg, 0, sizeof(msg));
for (i = 0; i < num; i++) {
......@@ -538,20 +579,19 @@ static int drm_dp_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs,
err = drm_dp_i2c_do_msg(aux, &msg);
if (err < 0)
break;
/*
* Many hardware implementations support FIFOs larger than a
* single byte, but it has been empirically determined that
* transferring data in larger chunks can actually lead to
* decreased performance. Therefore each message is simply
* transferred byte-by-byte.
/* We want each transaction to be as large as possible, but
* we'll go to smaller sizes if the hardware gives us a
* short reply.
*/
for (j = 0; j < msgs[i].len; j++) {
transfer_size = dp_aux_i2c_transfer_size;
for (j = 0; j < msgs[i].len; j += msg.size) {
msg.buffer = msgs[i].buf + j;
msg.size = 1;
msg.size = min(transfer_size, msgs[i].len - j);
err = drm_dp_i2c_do_msg(aux, &msg);
err = drm_dp_i2c_drain_msg(aux, &msg);
if (err < 0)
break;
transfer_size = err;
}
if (err < 0)
break;
......
......@@ -70,7 +70,7 @@ void drm_err(const char *format, ...)
vaf.fmt = format;
vaf.va = &args;
printk(KERN_ERR "[" DRM_NAME ":%pf] *ERROR* %pV",
printk(KERN_ERR "[" DRM_NAME ":%ps] *ERROR* %pV",
__builtin_return_address(0), &vaf);
va_end(args);
......
......@@ -304,7 +304,7 @@ static int drm_fbdev_cma_create(struct drm_fb_helper *helper,
}
drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth);
drm_fb_helper_fill_var(fbi, helper, fb->width, fb->height);
drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height);
offset = fbi->var.xoffset * bytes_per_pixel;
offset += fbi->var.yoffset * fb->pitches[0];
......
......@@ -1034,23 +1034,45 @@ static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper,
crtc_count = 0;
for (i = 0; i < fb_helper->crtc_count; i++) {
struct drm_display_mode *desired_mode;
int x, y;
struct drm_mode_set *mode_set;
int x, y, j;
/* in case of tile group, are we the last tile vert or horiz?
* If no tile group you are always the last one both vertically
* and horizontally
*/
bool lastv = true, lasth = true;
desired_mode = fb_helper->crtc_info[i].desired_mode;
mode_set = &fb_helper->crtc_info[i].mode_set;
if (!desired_mode)
continue;
crtc_count++;
x = fb_helper->crtc_info[i].x;
y = fb_helper->crtc_info[i].y;
if (desired_mode) {
if (gamma_size == 0)
gamma_size = fb_helper->crtc_info[i].mode_set.crtc->gamma_size;
if (desired_mode->hdisplay + x < sizes.fb_width)
sizes.fb_width = desired_mode->hdisplay + x;
if (desired_mode->vdisplay + y < sizes.fb_height)
sizes.fb_height = desired_mode->vdisplay + y;
if (desired_mode->hdisplay + x > sizes.surface_width)
sizes.surface_width = desired_mode->hdisplay + x;
if (desired_mode->vdisplay + y > sizes.surface_height)
sizes.surface_height = desired_mode->vdisplay + y;
crtc_count++;
sizes.surface_width = max_t(u32, desired_mode->hdisplay + x, sizes.surface_width);
sizes.surface_height = max_t(u32, desired_mode->vdisplay + y, sizes.surface_height);
for (j = 0; j < mode_set->num_connectors; j++) {
struct drm_connector *connector = mode_set->connectors[j];
if (connector->has_tile) {
lasth = (connector->tile_h_loc == (connector->num_h_tile - 1));
lastv = (connector->tile_v_loc == (connector->num_v_tile - 1));
/* cloning to multiple tiles is just crazy-talk, so: */
break;
}
}
if (lasth)
sizes.fb_width = min_t(u32, desired_mode->hdisplay + x, sizes.fb_width);
if (lastv)
sizes.fb_height = min_t(u32, desired_mode->vdisplay + y, sizes.fb_height);
}
if (crtc_count == 0 || sizes.fb_width == -1 || sizes.fb_height == -1) {
......
......@@ -37,6 +37,7 @@
#include <drm/drmP.h>
#include <drm/drm_gem.h>
#include "drm_internal.h"
#include "drm_legacy.h"
/**
......
......@@ -1016,7 +1016,7 @@ static int compat_drm_wait_vblank(struct file *file, unsigned int cmd,
return 0;
}
drm_ioctl_compat_t *drm_compat_ioctls[] = {
static drm_ioctl_compat_t *drm_compat_ioctls[] = {
[DRM_IOCTL_NR(DRM_IOCTL_VERSION32)] = compat_drm_version,
[DRM_IOCTL_NR(DRM_IOCTL_GET_UNIQUE32)] = compat_drm_getunique,
[DRM_IOCTL_NR(DRM_IOCTL_GET_MAP32)] = compat_drm_getmap,
......
......@@ -27,6 +27,7 @@
#include <linux/dma-mapping.h>
#include <linux/export.h>
#include <drm/drmP.h>
#include "drm_internal.h"
#include "drm_legacy.h"
/**
......
......@@ -353,12 +353,13 @@ static struct drm_plane *create_primary_plane(struct drm_device *dev)
if (primary == NULL) {
DRM_DEBUG_KMS("Failed to allocate primary plane\n");
return NULL;
}
/*
* Remove the format_default field from drm_plane when dropping
* this helper.
*/
primary->format_default = true;
}
/* possible_crtc's will be filled in later by crtc_init */
ret = drm_universal_plane_init(dev, primary, 0,
......
......@@ -41,6 +41,7 @@
#include <linux/slab.h>
#endif
#include <asm/pgtable.h>
#include "drm_internal.h"
#include "drm_legacy.h"
struct drm_vma_entry {
......
......@@ -76,6 +76,7 @@ static struct fb_ops exynos_drm_fb_ops = {
};
static int exynos_drm_fbdev_update(struct drm_fb_helper *helper,
struct drm_fb_helper_surface_size *sizes,
struct drm_framebuffer *fb)
{
struct fb_info *fbi = helper->fbdev;
......@@ -85,7 +86,7 @@ static int exynos_drm_fbdev_update(struct drm_fb_helper *helper,
unsigned long offset;
drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth);
drm_fb_helper_fill_var(fbi, helper, fb->width, fb->height);
drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height);
/* RGB formats use only one buffer */
buffer = exynos_drm_fb_buffer(fb, 0);
......@@ -189,7 +190,7 @@ static int exynos_drm_fbdev_create(struct drm_fb_helper *helper,
goto err_destroy_framebuffer;
}
ret = exynos_drm_fbdev_update(helper, helper->fb);
ret = exynos_drm_fbdev_update(helper, sizes, helper->fb);
if (ret < 0)
goto err_dealloc_cmap;
......
......@@ -106,7 +106,7 @@ static int rockchip_drm_fbdev_create(struct drm_fb_helper *helper,
fb = helper->fb;
drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth);
drm_fb_helper_fill_var(fbi, helper, fb->width, fb->height);
drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height);
offset = fbi->var.xoffset * bytes_per_pixel;
offset += fbi->var.yoffset * fb->pitches[0];
......
......@@ -915,7 +915,7 @@ struct drm_bridge {
};
/**
* struct struct drm_atomic_state - the global state object for atomic updates
* struct drm_atomic_state - the global state object for atomic updates
* @dev: parent DRM device
* @allow_modeset: allow full modeset
* @legacy_cursor_update: hint to enforce legacy cursor ioctl semantics
......
......@@ -42,6 +42,8 @@
* 1.2 formally includes both eDP and DPI definitions.
*/
#define DP_AUX_MAX_PAYLOAD_BYTES 16
#define DP_AUX_I2C_WRITE 0x0
#define DP_AUX_I2C_READ 0x1
#define DP_AUX_I2C_STATUS 0x2
......@@ -680,6 +682,9 @@ struct drm_dp_aux_msg {
* transactions. The drm_dp_aux_register_i2c_bus() function registers an
* I2C adapter that can be passed to drm_probe_ddc(). Upon removal, drivers
* should call drm_dp_aux_unregister_i2c_bus() to remove the I2C adapter.
* The I2C adapter uses long transfers by default; if a partial response is
* received, the adapter will drop down to the size given by the partial
* response for this transaction only.
*
* Note that the aux helper code assumes that the .transfer() function
* only modifies the reply field of the drm_dp_aux_msg structure. The
......
......@@ -44,6 +44,25 @@ struct drm_fb_helper_crtc {
int x, y;
};
/**
* struct drm_fb_helper_surface_size - describes fbdev size and scanout surface size
* @fb_width: fbdev width
* @fb_height: fbdev height
* @surface_width: scanout buffer width
* @surface_height: scanout buffer height
* @surface_bpp: scanout buffer bpp
* @surface_depth: scanout buffer depth
*
* Note that the scanout surface width/height may be larger than the fbdev
* width/height. In case of multiple displays, the scanout surface is sized
* according to the largest width/height (so it is large enough for all CRTCs
* to scanout). But the fbdev width/height is sized to the minimum width/
* height of all the displays. This ensures that fbcon fits on the smallest
* of the attached displays.
*
* So what is passed to drm_fb_helper_fill_var() should be fb_width/fb_height,
* rather than the surface size.
*/
struct drm_fb_helper_surface_size {
u32 fb_width;
u32 fb_height;
......
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