Commit fe03d5ee authored by Igor M. Liplianin's avatar Igor M. Liplianin Committed by Mauro Carvalho Chehab

V4L/DVB (8991): Added support for DVBWorld 2104 and TeVii S650 USB DVB-S2 cards

Added support for DVBWorld 2104 and TeVii S650 USB DVB-S2 cards
This cards based on cx24116 demodulator.
Signed-off-by: default avatarIgor M. Liplianin <liplianin@me.by>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@redhat.com>
parent 2cf801e3
/* DVB USB framework compliant Linux driver for the DVBWorld DVB-S 2102 Card /* DVB USB framework compliant Linux driver for the
* DVBWorld DVB-S 2101, 2102, DVB-S2 2104 Card
* *
* Copyright (C) 2008 Igor M. Liplianin (liplianin@me.by) * Copyright (C) 2008 Igor M. Liplianin (liplianin@me.by)
* *
...@@ -12,18 +13,23 @@ ...@@ -12,18 +13,23 @@
#include "dw2102.h" #include "dw2102.h"
#include "stv0299.h" #include "stv0299.h"
#include "z0194a.h" #include "z0194a.h"
#include "cx24116.h"
#ifndef USB_PID_DW2102 #ifndef USB_PID_DW2102
#define USB_PID_DW2102 0x2102 #define USB_PID_DW2102 0x2102
#endif #endif
#ifndef USB_PID_DW2104
#define USB_PID_DW2104 0x2104
#endif
#define DW2102_READ_MSG 0 #define DW2102_READ_MSG 0
#define DW2102_WRITE_MSG 1 #define DW2102_WRITE_MSG 1
#define REG_1F_SYMBOLRATE_BYTE0 0x1f #define REG_1F_SYMBOLRATE_BYTE0 0x1f
#define REG_20_SYMBOLRATE_BYTE1 0x20 #define REG_20_SYMBOLRATE_BYTE1 0x20
#define REG_21_SYMBOLRATE_BYTE2 0x21 #define REG_21_SYMBOLRATE_BYTE2 0x21
/* on my own*/
#define DW2102_VOLTAGE_CTRL (0x1800) #define DW2102_VOLTAGE_CTRL (0x1800)
#define DW2102_RC_QUERY (0x1a00) #define DW2102_RC_QUERY (0x1a00)
...@@ -35,22 +41,27 @@ struct dw2102_rc_keys { ...@@ -35,22 +41,27 @@ struct dw2102_rc_keys {
u32 event; u32 event;
}; };
/* debug */
static int dvb_usb_dw2102_debug;
module_param_named(debug, dvb_usb_dw2102_debug, int, 0644);
MODULE_PARM_DESC(debug, "set debugging level (1=info 2=xfer (or-able))." DVB_USB_DEBUG_STATUS);
DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
static int dw2102_op_rw(struct usb_device *dev, u8 request, u16 value, static int dw2102_op_rw(struct usb_device *dev, u8 request, u16 value,
u8 *data, u16 len, int flags) u16 index, u8 * data, u16 len, int flags)
{ {
int ret; int ret;
u8 u8buf[len]; u8 u8buf[len];
unsigned int pipe = (flags == DW2102_READ_MSG) ? unsigned int pipe = (flags == DW2102_READ_MSG) ?
usb_rcvctrlpipe(dev, 0) : usb_sndctrlpipe(dev, 0); usb_rcvctrlpipe(dev, 0) : usb_sndctrlpipe(dev, 0);
u8 request_type = (flags == DW2102_READ_MSG) ? USB_DIR_IN : USB_DIR_OUT; u8 request_type = (flags == DW2102_READ_MSG) ? USB_DIR_IN : USB_DIR_OUT;
if (flags == DW2102_WRITE_MSG) if (flags == DW2102_WRITE_MSG)
memcpy(u8buf, data, len); memcpy(u8buf, data, len);
ret = usb_control_msg(dev, pipe, request, ret = usb_control_msg(dev, pipe, request, request_type | USB_TYPE_VENDOR,
request_type | USB_TYPE_VENDOR, value, 0 , u8buf, len, 2000); value, index , u8buf, len, 2000);
if (flags == DW2102_READ_MSG) if (flags == DW2102_READ_MSG)
memcpy(data, u8buf, len); memcpy(data, u8buf, len);
...@@ -65,7 +76,6 @@ static int dw2102_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], ...@@ -65,7 +76,6 @@ static int dw2102_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[],
struct dvb_usb_device *d = i2c_get_adapdata(adap); struct dvb_usb_device *d = i2c_get_adapdata(adap);
int i = 0, ret = 0; int i = 0, ret = 0;
u8 buf6[] = {0x2c, 0x05, 0xc0, 0, 0, 0, 0}; u8 buf6[] = {0x2c, 0x05, 0xc0, 0, 0, 0, 0};
u8 request;
u16 value; u16 value;
if (!d) if (!d)
...@@ -76,12 +86,11 @@ struct dvb_usb_device *d = i2c_get_adapdata(adap); ...@@ -76,12 +86,11 @@ struct dvb_usb_device *d = i2c_get_adapdata(adap);
switch (num) { switch (num) {
case 2: case 2:
/* read stv0299 register */ /* read stv0299 register */
request = 0xb5;
value = msg[0].buf[0];/* register */ value = msg[0].buf[0];/* register */
for (i = 0; i < msg[1].len; i++) { for (i = 0; i < msg[1].len; i++) {
value = value + i; value = value + i;
ret = dw2102_op_rw(d->udev, 0xb5, ret = dw2102_op_rw(d->udev, 0xb5, value, 0,
value, buf6, 2, DW2102_READ_MSG); buf6, 2, DW2102_READ_MSG);
msg[1].buf[i] = buf6[0]; msg[1].buf[i] = buf6[0];
} }
...@@ -93,8 +102,8 @@ struct dvb_usb_device *d = i2c_get_adapdata(adap); ...@@ -93,8 +102,8 @@ struct dvb_usb_device *d = i2c_get_adapdata(adap);
buf6[0] = 0x2a; buf6[0] = 0x2a;
buf6[1] = msg[0].buf[0]; buf6[1] = msg[0].buf[0];
buf6[2] = msg[0].buf[1]; buf6[2] = msg[0].buf[1];
ret = dw2102_op_rw(d->udev, 0xb2, ret = dw2102_op_rw(d->udev, 0xb2, 0, 0,
0, buf6, 3, DW2102_WRITE_MSG); buf6, 3, DW2102_WRITE_MSG);
break; break;
case 0x60: case 0x60:
if (msg[0].flags == 0) { if (msg[0].flags == 0) {
...@@ -106,26 +115,26 @@ struct dvb_usb_device *d = i2c_get_adapdata(adap); ...@@ -106,26 +115,26 @@ struct dvb_usb_device *d = i2c_get_adapdata(adap);
buf6[4] = msg[0].buf[1]; buf6[4] = msg[0].buf[1];
buf6[5] = msg[0].buf[2]; buf6[5] = msg[0].buf[2];
buf6[6] = msg[0].buf[3]; buf6[6] = msg[0].buf[3];
ret = dw2102_op_rw(d->udev, 0xb2, ret = dw2102_op_rw(d->udev, 0xb2, 0, 0,
0, buf6, 7, DW2102_WRITE_MSG); buf6, 7, DW2102_WRITE_MSG);
} else { } else {
/* write to tuner pll */ /* read from tuner */
ret = dw2102_op_rw(d->udev, 0xb5, ret = dw2102_op_rw(d->udev, 0xb5, 0,0,
0, buf6, 1, DW2102_READ_MSG); buf6, 1, DW2102_READ_MSG);
msg[0].buf[0] = buf6[0]; msg[0].buf[0] = buf6[0];
} }
break; break;
case (DW2102_RC_QUERY): case (DW2102_RC_QUERY):
ret = dw2102_op_rw(d->udev, 0xb8, ret = dw2102_op_rw(d->udev, 0xb8, 0, 0,
0, buf6, 2, DW2102_READ_MSG); buf6, 2, DW2102_READ_MSG);
msg[0].buf[0] = buf6[0]; msg[0].buf[0] = buf6[0];
msg[0].buf[1] = buf6[1]; msg[0].buf[1] = buf6[1];
break; break;
case (DW2102_VOLTAGE_CTRL): case (DW2102_VOLTAGE_CTRL):
buf6[0] = 0x30; buf6[0] = 0x30;
buf6[1] = msg[0].buf[0]; buf6[1] = msg[0].buf[0];
ret = dw2102_op_rw(d->udev, 0xb2, ret = dw2102_op_rw(d->udev, 0xb2, 0, 0,
0, buf6, 2, DW2102_WRITE_MSG); buf6, 2, DW2102_WRITE_MSG);
break; break;
} }
...@@ -136,6 +145,88 @@ struct dvb_usb_device *d = i2c_get_adapdata(adap); ...@@ -136,6 +145,88 @@ struct dvb_usb_device *d = i2c_get_adapdata(adap);
return num; return num;
} }
static int dw2104_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], int num)
{
struct dvb_usb_device *d = i2c_get_adapdata(adap);
int ret = 0;
int len, i;
if (!d)
return -ENODEV;
if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
return -EAGAIN;
switch (num) {
case 2: {
/* read */
/* first write first register number */
u8 ibuf [msg[1].len + 2], obuf[3];
obuf[0] = 0xaa;
obuf[1] = msg[0].len;
obuf[2] = msg[0].buf[0];
ret = dw2102_op_rw(d->udev, 0xc2, 0, 0,
obuf, msg[0].len + 2, DW2102_WRITE_MSG);
/* second read registers */
ret = dw2102_op_rw(d->udev, 0xc3, 0xab , 0,
ibuf, msg[1].len + 2, DW2102_READ_MSG);
memcpy(msg[1].buf, ibuf + 2, msg[1].len);
break;
}
case 1:
switch (msg[0].addr) {
case 0x55: {
if (msg[0].buf[0] == 0xf7) {
/* firmware */
/* Write in small blocks */
u8 obuf[19];
obuf[0] = 0xaa;
obuf[1] = 0x11;
obuf[2] = 0xf7;
len = msg[0].len - 1;
i = 1;
do {
memcpy(obuf + 3, msg[0].buf + i, (len > 16 ? 16 : len));
ret = dw2102_op_rw(d->udev, 0xc2, 0, 0,
obuf, (len > 16 ? 16 : len) + 3, DW2102_WRITE_MSG);
i += 16;
len -= 16;
} while (len > 0);
} else {
/* write to register */
u8 obuf[msg[0].len + 2];
obuf[0] = 0xaa;
obuf[1] = msg[0].len;
memcpy(obuf + 2, msg[0].buf, msg[0].len);
ret = dw2102_op_rw(d->udev, 0xc2, 0, 0,
obuf, msg[0].len + 2, DW2102_WRITE_MSG);
}
break;
}
case(DW2102_RC_QUERY): {
u8 ibuf[2];
ret = dw2102_op_rw(d->udev, 0xb8, 0, 0,
ibuf, 2, DW2102_READ_MSG);
memcpy(msg[0].buf, ibuf , 2);
break;
}
case(DW2102_VOLTAGE_CTRL): {
u8 obuf[2];
obuf[0] = 0x30;
obuf[1] = msg[0].buf[0];
ret = dw2102_op_rw(d->udev, 0xb2, 0, 0,
obuf, 2, DW2102_WRITE_MSG);
break;
}
}
break;
}
mutex_unlock(&d->i2c_mutex);
return num;
}
static u32 dw2102_i2c_func(struct i2c_adapter *adapter) static u32 dw2102_i2c_func(struct i2c_adapter *adapter)
{ {
return I2C_FUNC_I2C; return I2C_FUNC_I2C;
...@@ -146,6 +237,34 @@ static struct i2c_algorithm dw2102_i2c_algo = { ...@@ -146,6 +237,34 @@ static struct i2c_algorithm dw2102_i2c_algo = {
.functionality = dw2102_i2c_func, .functionality = dw2102_i2c_func,
}; };
static struct i2c_algorithm dw2104_i2c_algo = {
.master_xfer = dw2104_i2c_transfer,
.functionality = dw2102_i2c_func,
};
static int dw2102_read_mac_address(struct dvb_usb_device *d, u8 mac[6])
{
int i;
u8 ibuf[] = {0, 0};
u8 eeprom[256], eepromline[16];
for (i = 0; i < 256; i++) {
if (dw2102_op_rw(d->udev, 0xb6, 0xa0 , i, ibuf, 2, DW2102_READ_MSG) < 0) {
err("read eeprom failed.");
return -1;
} else {
eepromline[i%16] = ibuf[0];
eeprom[i] = ibuf[0];
}
if ((i % 16) == 15) {
deb_xfer("%02x: ", i - 15);
debug_dump(eepromline, 16, deb_xfer);
}
}
memcpy(mac, eeprom + 8, 6);
return 0;
};
static int dw2102_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage) static int dw2102_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
{ {
static u8 command_13v[1] = {0x00}; static u8 command_13v[1] = {0x00};
...@@ -163,6 +282,22 @@ static int dw2102_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage) ...@@ -163,6 +282,22 @@ static int dw2102_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
return 0; return 0;
} }
static struct cx24116_config dw2104_config = {
.demod_address = 0x55,
/*.mpg_clk_pos_pol = 0x01,*/
};
static int dw2104_frontend_attach(struct dvb_usb_adapter *d)
{
if ((d->fe = dvb_attach(cx24116_attach, &dw2104_config,
&d->dev->i2c_adap)) != NULL) {
d->fe->ops.set_voltage = dw2102_set_voltage;
info("Attached cx24116!\n");
return 0;
}
return -EIO;
}
static int dw2102_frontend_attach(struct dvb_usb_adapter *d) static int dw2102_frontend_attach(struct dvb_usb_adapter *d)
{ {
d->fe = dvb_attach(stv0299_attach, &sharp_z0194a_config, d->fe = dvb_attach(stv0299_attach, &sharp_z0194a_config,
...@@ -249,6 +384,8 @@ static int dw2102_rc_query(struct dvb_usb_device *d, u32 *event, int *state) ...@@ -249,6 +384,8 @@ static int dw2102_rc_query(struct dvb_usb_device *d, u32 *event, int *state)
static struct usb_device_id dw2102_table[] = { static struct usb_device_id dw2102_table[] = {
{USB_DEVICE(USB_VID_CYPRESS, USB_PID_DW2102)}, {USB_DEVICE(USB_VID_CYPRESS, USB_PID_DW2102)},
{USB_DEVICE(USB_VID_CYPRESS, 0x2101)}, {USB_DEVICE(USB_VID_CYPRESS, 0x2101)},
{USB_DEVICE(USB_VID_CYPRESS, 0x2104)},
{USB_DEVICE(0x9022, 0xd650)},
{ } { }
}; };
...@@ -273,25 +410,23 @@ static int dw2102_load_firmware(struct usb_device *dev, ...@@ -273,25 +410,23 @@ static int dw2102_load_firmware(struct usb_device *dev,
return ret; return ret;
} }
break; break;
case USB_PID_DW2102: default:
fw = frmwr; fw = frmwr;
break; break;
} }
info("start downloading DW2102 firmware"); info("start downloading DW210X firmware");
p = kmalloc(fw->size, GFP_KERNEL); p = kmalloc(fw->size, GFP_KERNEL);
reset = 1; reset = 1;
/*stop the CPU*/ /*stop the CPU*/
dw2102_op_rw(dev, 0xa0, 0x7f92, &reset, 1, DW2102_WRITE_MSG); dw2102_op_rw(dev, 0xa0, 0x7f92, 0, &reset, 1, DW2102_WRITE_MSG);
dw2102_op_rw(dev, 0xa0, 0xe600, &reset, 1, DW2102_WRITE_MSG); dw2102_op_rw(dev, 0xa0, 0xe600, 0, &reset, 1, DW2102_WRITE_MSG);
if (p != NULL) { if (p != NULL) {
memcpy(p, fw->data, fw->size); memcpy(p, fw->data, fw->size);
for (i = 0; i < fw->size; i += 0x40) { for (i = 0; i < fw->size; i += 0x40) {
b = (u8 *) p + i; b = (u8 *) p + i;
if (dw2102_op_rw if (dw2102_op_rw(dev, 0xa0, i, 0, b , 0x40,
(dev, 0xa0, i, b , 0x40, DW2102_WRITE_MSG) != 0x40) {
DW2102_WRITE_MSG) != 0x40
) {
err("error while transferring firmware"); err("error while transferring firmware");
ret = -EINVAL; ret = -EINVAL;
break; break;
...@@ -299,43 +434,45 @@ static int dw2102_load_firmware(struct usb_device *dev, ...@@ -299,43 +434,45 @@ static int dw2102_load_firmware(struct usb_device *dev,
} }
/* restart the CPU */ /* restart the CPU */
reset = 0; reset = 0;
if (ret || dw2102_op_rw if (ret || dw2102_op_rw(dev, 0xa0, 0x7f92, 0, &reset, 1,
(dev, 0xa0, 0x7f92, &reset, 1, DW2102_WRITE_MSG) != 1) {
DW2102_WRITE_MSG) != 1) {
err("could not restart the USB controller CPU."); err("could not restart the USB controller CPU.");
ret = -EINVAL; ret = -EINVAL;
} }
if (ret || dw2102_op_rw if (ret || dw2102_op_rw(dev, 0xa0, 0xe600, 0, &reset, 1,
(dev, 0xa0, 0xe600, &reset, 1, DW2102_WRITE_MSG) != 1) {
DW2102_WRITE_MSG) != 1) {
err("could not restart the USB controller CPU."); err("could not restart the USB controller CPU.");
ret = -EINVAL; ret = -EINVAL;
} }
/* init registers */ /* init registers */
switch (dev->descriptor.idProduct) { switch (dev->descriptor.idProduct) {
case USB_PID_DW2104:
case 0xd650:
reset = 1;
dw2102_op_rw(dev, 0xc4, 0x0000, 0, &reset, 1,
DW2102_WRITE_MSG);
reset = 0;
dw2102_op_rw(dev, 0xbf, 0x0040, 0, &reset, 0,
DW2102_WRITE_MSG);
break;
case USB_PID_DW2102: case USB_PID_DW2102:
dw2102_op_rw dw2102_op_rw(dev, 0xbf, 0x0040, 0, &reset, 0,
(dev, 0xbf, 0x0040, &reset, 0, DW2102_WRITE_MSG);
DW2102_WRITE_MSG); dw2102_op_rw(dev, 0xb9, 0x0000, 0, &reset16[0], 2,
dw2102_op_rw DW2102_READ_MSG);
(dev, 0xb9, 0x0000, &reset16[0], 2,
DW2102_READ_MSG);
break; break;
case 0x2101: case 0x2101:
dw2102_op_rw dw2102_op_rw(dev, 0xbc, 0x0030, 0, &reset16[0], 2,
(dev, 0xbc, 0x0030, &reset16[0], 2, DW2102_READ_MSG);
DW2102_READ_MSG); dw2102_op_rw(dev, 0xba, 0x0000, 0, &reset16[0], 7,
dw2102_op_rw DW2102_READ_MSG);
(dev, 0xba, 0x0000, &reset16[0], 7, dw2102_op_rw(dev, 0xba, 0x0000, 0, &reset16[0], 7,
DW2102_READ_MSG); DW2102_READ_MSG);
dw2102_op_rw dw2102_op_rw(dev, 0xb9, 0x0000, 0, &reset16[0], 2,
(dev, 0xba, 0x0000, &reset16[0], 7, DW2102_READ_MSG);
DW2102_READ_MSG);
dw2102_op_rw
(dev, 0xb9, 0x0000, &reset16[0], 2,
DW2102_READ_MSG);
break; break;
} }
msleep(100);
kfree(p); kfree(p);
} }
return ret; return ret;
...@@ -358,7 +495,8 @@ static struct dvb_usb_device_properties dw2102_properties = { ...@@ -358,7 +495,8 @@ static struct dvb_usb_device_properties dw2102_properties = {
/* parameter for the MPEG2-data transfer */ /* parameter for the MPEG2-data transfer */
.num_adapters = 1, .num_adapters = 1,
.download_firmware = dw2102_load_firmware, .download_firmware = dw2102_load_firmware,
.adapter = { .read_mac_address = dw2102_read_mac_address,
.adapter = {
{ {
.frontend_attach = dw2102_frontend_attach, .frontend_attach = dw2102_frontend_attach,
.streaming_ctrl = NULL, .streaming_ctrl = NULL,
...@@ -388,11 +526,64 @@ static struct dvb_usb_device_properties dw2102_properties = { ...@@ -388,11 +526,64 @@ static struct dvb_usb_device_properties dw2102_properties = {
} }
}; };
static struct dvb_usb_device_properties dw2104_properties = {
.caps = DVB_USB_IS_AN_I2C_ADAPTER,
.usb_ctrl = DEVICE_SPECIFIC,
.firmware = "dvb-usb-dw2104.fw",
.size_of_priv = sizeof(struct dw2102_state),
.no_reconnect = 1,
.i2c_algo = &dw2104_i2c_algo,
.rc_key_map = dw2102_rc_keys,
.rc_key_map_size = ARRAY_SIZE(dw2102_rc_keys),
.rc_interval = 150,
.rc_query = dw2102_rc_query,
.generic_bulk_ctrl_endpoint = 0x81,
/* parameter for the MPEG2-data transfer */
.num_adapters = 1,
.download_firmware = dw2102_load_firmware,
.read_mac_address = dw2102_read_mac_address,
.adapter = {
{
.frontend_attach = dw2104_frontend_attach,
.streaming_ctrl = NULL,
/*.tuner_attach = dw2104_tuner_attach,*/
.stream = {
.type = USB_BULK,
.count = 8,
.endpoint = 0x82,
.u = {
.bulk = {
.buffersize = 4096,
}
}
},
}
},
.num_device_descs = 2,
.devices = {
{ "DVBWorld DW2104 USB2.0",
{&dw2102_table[2], NULL},
{NULL},
},
{ "TeVii S650 USB2.0",
{&dw2102_table[3], NULL},
{NULL},
},
}
};
static int dw2102_probe(struct usb_interface *intf, static int dw2102_probe(struct usb_interface *intf,
const struct usb_device_id *id) const struct usb_device_id *id)
{ {
return dvb_usb_device_init(intf, &dw2102_properties, if (0 == dvb_usb_device_init(intf, &dw2102_properties,
THIS_MODULE, NULL, adapter_nr); THIS_MODULE, NULL, adapter_nr) ||
0 == dvb_usb_device_init(intf, &dw2104_properties,
THIS_MODULE, NULL, adapter_nr)) {
return 0;
}
return -ENODEV;
} }
static struct usb_driver dw2102_driver = { static struct usb_driver dw2102_driver = {
...@@ -420,6 +611,6 @@ module_init(dw2102_module_init); ...@@ -420,6 +611,6 @@ module_init(dw2102_module_init);
module_exit(dw2102_module_exit); module_exit(dw2102_module_exit);
MODULE_AUTHOR("Igor M. Liplianin (c) liplianin@me.by"); MODULE_AUTHOR("Igor M. Liplianin (c) liplianin@me.by");
MODULE_DESCRIPTION("Driver for DVBWorld DVB-S 2101 2102 USB2.0 device"); MODULE_DESCRIPTION("Driver for DVBWorld DVB-S 2101, 2102, DVB-S2 2104 USB2.0 device");
MODULE_VERSION("0.1"); MODULE_VERSION("0.1");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
...@@ -4,6 +4,5 @@ ...@@ -4,6 +4,5 @@
#define DVB_USB_LOG_PREFIX "dw2102" #define DVB_USB_LOG_PREFIX "dw2102"
#include "dvb-usb.h" #include "dvb-usb.h"
extern int dvb_usb_dw2102_debug;
#define deb_xfer(args...) dprintk(dvb_usb_dw2102_debug, 0x02, args) #define deb_xfer(args...) dprintk(dvb_usb_dw2102_debug, 0x02, args)
#endif #endif
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