Commit 4171a9aa authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'regmap-v6.5' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap

Pull regmap updates from Mark Brown:
 "Another busy release for regmap with the second half of the maple tree
  register cache implementation, there's some smaller optimisations that
  could be done but this should now be able to replace the rbtree cache
  for most devices.

  We also had a followup from Aidan MacDonald's refactoring of some of
  the regmap-irq interfaces, the conversion is complete so the old
  interfaces are removed. This means that even with the new features for
  the maple tree cache we'd have a nice negative diffstat were it not
  for the addition of a bunch more KUnit coverage.

  There's one GPIO patch in here, it was a dependency for a cleanup of
  an API in the regmap-irq code for which the gpio-104-dio-48e driver
  was the only user.

  Highlights:

   - The maple tree cache can now load in default values more
     efficiently, and is capabale of syncing multiple registers
     in a single write during cache sync

   - More KUnit coverage, including some coverage for raw I/O
     and a dummy RAM backed cache to support it

   - Removal of several old interfaces in regmap-irq now all
     users have been modernised"

* tag 'regmap-v6.5' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap: (23 commits)
  regmap: Allow reads from write only registers with the flat cache
  regmap: Drop early readability check
  regmap: Check for register readability before checking cache during read
  regmap: Add test to make sure we don't sync to read only registers
  regmap: Add a test case for write only registers
  regmap: Add test that writes to write only registers are prevented
  regmap: Add debugfs file for forcing field writes
  regmap: Don't check for changes in regcache_set_val()
  regmap: maple: Implement block sync for the maple tree cache
  regmap: Provide basic KUnit coverage for the raw register I/O
  regmap: Provide a ram backed regmap with raw support
  regmap: Add missing cache_only checks
  regmap: regmap-irq: Move handle_post_irq to before pm_runtime_put
  regmap: Load register defaults in blocks rather than register by register
  regmap: mmio: Allow passing an empty config->reg_stride
  regmap-irq: Drop backward compatibility for inverted mask/unmask
  regmap-irq: Minor adjustments to .handle_mask_sync()
  regmap-irq: Remove support for not_fixed_stride
  regmap-irq: Remove type registers
  regmap-irq: Remove virtual registers
  ...
parents 1b2c92a1 d0c99ffe
......@@ -8,7 +8,7 @@ obj-$(CONFIG_DEBUG_FS) += regmap-debugfs.o
obj-$(CONFIG_REGMAP_KUNIT) += regmap-kunit.o
obj-$(CONFIG_REGMAP_AC97) += regmap-ac97.o
obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o
obj-$(CONFIG_REGMAP_RAM) += regmap-ram.o
obj-$(CONFIG_REGMAP_RAM) += regmap-ram.o regmap-raw-ram.o
obj-$(CONFIG_REGMAP_SLIMBUS) += regmap-slimbus.o
obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o
obj-$(CONFIG_REGMAP_SPMI) += regmap-spmi.o
......
......@@ -125,6 +125,9 @@ struct regmap {
int reg_stride;
int reg_stride_order;
/* If set, will always write field to HW. */
bool force_write_field;
/* regcache specific members */
const struct regcache_ops *cache_ops;
enum regcache_type cache_type;
......@@ -257,6 +260,8 @@ int regcache_sync_block(struct regmap *map, void *block,
unsigned long *cache_present,
unsigned int block_base, unsigned int start,
unsigned int end);
bool regcache_reg_needs_sync(struct regmap *map, unsigned int reg,
unsigned int val);
static inline const void *regcache_get_val_addr(struct regmap *map,
const void *base,
......@@ -267,7 +272,7 @@ static inline const void *regcache_get_val_addr(struct regmap *map,
unsigned int regcache_get_val(struct regmap *map, const void *base,
unsigned int idx);
bool regcache_set_val(struct regmap *map, void *base, unsigned int idx,
void regcache_set_val(struct regmap *map, void *base, unsigned int idx,
unsigned int val);
int regcache_lookup_reg(struct regmap *map, unsigned int reg);
int regcache_sync_val(struct regmap *map, unsigned int reg, unsigned int val);
......@@ -312,6 +317,7 @@ struct regmap_ram_data {
unsigned int *vals; /* Allocatd by caller */
bool *read;
bool *written;
enum regmap_endian reg_endian;
};
/*
......@@ -326,5 +332,12 @@ struct regmap *__regmap_init_ram(const struct regmap_config *config,
#define regmap_init_ram(config, data) \
__regmap_lockdep_wrapper(__regmap_init_ram, #config, config, data)
struct regmap *__regmap_init_raw_ram(const struct regmap_config *config,
struct regmap_ram_data *data,
struct lock_class_key *lock_key,
const char *lock_name);
#define regmap_init_raw_ram(config, data) \
__regmap_lockdep_wrapper(__regmap_init_raw_ram, #config, config, data)
#endif
......@@ -186,6 +186,55 @@ static int regcache_maple_drop(struct regmap *map, unsigned int min,
return ret;
}
static int regcache_maple_sync_block(struct regmap *map, unsigned long *entry,
struct ma_state *mas,
unsigned int min, unsigned int max)
{
void *buf;
unsigned long r;
size_t val_bytes = map->format.val_bytes;
int ret = 0;
mas_pause(mas);
rcu_read_unlock();
/*
* Use a raw write if writing more than one register to a
* device that supports raw writes to reduce transaction
* overheads.
*/
if (max - min > 1 && regmap_can_raw_write(map)) {
buf = kmalloc(val_bytes * (max - min), map->alloc_flags);
if (!buf) {
ret = -ENOMEM;
goto out;
}
/* Render the data for a raw write */
for (r = min; r < max; r++) {
regcache_set_val(map, buf, r - min,
entry[r - mas->index]);
}
ret = _regmap_raw_write(map, min, buf, (max - min) * val_bytes,
false);
kfree(buf);
} else {
for (r = min; r < max; r++) {
ret = _regmap_write(map, r,
entry[r - mas->index]);
if (ret != 0)
goto out;
}
}
out:
rcu_read_lock();
return ret;
}
static int regcache_maple_sync(struct regmap *map, unsigned int min,
unsigned int max)
{
......@@ -194,8 +243,9 @@ static int regcache_maple_sync(struct regmap *map, unsigned int min,
MA_STATE(mas, mt, min, max);
unsigned long lmin = min;
unsigned long lmax = max;
unsigned int r;
unsigned int r, v, sync_start;
int ret;
bool sync_needed = false;
map->cache_bypass = true;
......@@ -203,18 +253,38 @@ static int regcache_maple_sync(struct regmap *map, unsigned int min,
mas_for_each(&mas, entry, max) {
for (r = max(mas.index, lmin); r <= min(mas.last, lmax); r++) {
mas_pause(&mas);
rcu_read_unlock();
ret = regcache_sync_val(map, r, entry[r - mas.index]);
v = entry[r - mas.index];
if (regcache_reg_needs_sync(map, r, v)) {
if (!sync_needed) {
sync_start = r;
sync_needed = true;
}
continue;
}
if (!sync_needed)
continue;
ret = regcache_maple_sync_block(map, entry, &mas,
sync_start, r);
if (ret != 0)
goto out;
rcu_read_lock();
sync_needed = false;
}
if (sync_needed) {
ret = regcache_maple_sync_block(map, entry, &mas,
sync_start, r);
if (ret != 0)
goto out;
sync_needed = false;
}
}
out:
rcu_read_unlock();
out:
map->cache_bypass = false;
return ret;
......@@ -242,11 +312,41 @@ static int regcache_maple_exit(struct regmap *map)
return 0;
}
static int regcache_maple_insert_block(struct regmap *map, int first,
int last)
{
struct maple_tree *mt = map->cache;
MA_STATE(mas, mt, first, last);
unsigned long *entry;
int i, ret;
entry = kcalloc(last - first + 1, sizeof(unsigned long), GFP_KERNEL);
if (!entry)
return -ENOMEM;
for (i = 0; i < last - first + 1; i++)
entry[i] = map->reg_defaults[first + i].def;
mas_lock(&mas);
mas_set_range(&mas, map->reg_defaults[first].reg,
map->reg_defaults[last].reg);
ret = mas_store_gfp(&mas, entry, GFP_KERNEL);
mas_unlock(&mas);
if (ret)
kfree(entry);
return ret;
}
static int regcache_maple_init(struct regmap *map)
{
struct maple_tree *mt;
int i;
int ret;
int range_start;
mt = kmalloc(sizeof(*mt), GFP_KERNEL);
if (!mt)
......@@ -255,13 +355,29 @@ static int regcache_maple_init(struct regmap *map)
mt_init(mt);
for (i = 0; i < map->num_reg_defaults; i++) {
ret = regcache_maple_write(map,
map->reg_defaults[i].reg,
map->reg_defaults[i].def);
if (ret)
if (!map->num_reg_defaults)
return 0;
range_start = 0;
/* Scan for ranges of contiguous registers */
for (i = 1; i < map->num_reg_defaults; i++) {
if (map->reg_defaults[i].reg !=
map->reg_defaults[i - 1].reg + 1) {
ret = regcache_maple_insert_block(map, range_start,
i - 1);
if (ret != 0)
goto err;
range_start = i;
}
}
/* Add the last block */
ret = regcache_maple_insert_block(map, range_start,
map->num_reg_defaults - 1);
if (ret != 0)
goto err;
return 0;
......
......@@ -279,7 +279,7 @@ int regcache_write(struct regmap *map,
return 0;
}
static bool regcache_reg_needs_sync(struct regmap *map, unsigned int reg,
bool regcache_reg_needs_sync(struct regmap *map, unsigned int reg,
unsigned int val)
{
int ret;
......@@ -561,17 +561,14 @@ void regcache_cache_bypass(struct regmap *map, bool enable)
}
EXPORT_SYMBOL_GPL(regcache_cache_bypass);
bool regcache_set_val(struct regmap *map, void *base, unsigned int idx,
void regcache_set_val(struct regmap *map, void *base, unsigned int idx,
unsigned int val)
{
if (regcache_get_val(map, base, idx) == val)
return true;
/* Use device native format if possible */
if (map->format.format_val) {
map->format.format_val(base + (map->cache_word_size * idx),
val, 0);
return false;
return;
}
switch (map->cache_word_size) {
......@@ -604,7 +601,6 @@ bool regcache_set_val(struct regmap *map, void *base, unsigned int idx,
default:
BUG();
}
return false;
}
unsigned int regcache_get_val(struct regmap *map, const void *base,
......
......@@ -636,6 +636,17 @@ void regmap_debugfs_init(struct regmap *map)
&regmap_cache_bypass_fops);
}
/*
* This could interfere with driver operation. Therefore, don't provide
* any real compile time configuration option for this feature. One will
* have to modify the source code directly in order to use it.
*/
#undef REGMAP_ALLOW_FORCE_WRITE_FIELD_DEBUGFS
#ifdef REGMAP_ALLOW_FORCE_WRITE_FIELD_DEBUGFS
debugfs_create_bool("force_write_field", 0600, map->debugfs,
&map->force_write_field);
#endif
next = rb_first(&map->range_tree);
while (next) {
range_node = rb_entry(next, struct regmap_range_node, node);
......
This diff is collapsed.
This diff is collapsed.
......@@ -448,7 +448,7 @@ static struct regmap_mmio_context *regmap_mmio_gen_context(struct device *dev,
if (min_stride < 0)
return ERR_PTR(min_stride);
if (config->reg_stride < min_stride)
if (config->reg_stride && config->reg_stride < min_stride)
return ERR_PTR(-EINVAL);
if (config->use_relaxed_mmio && config->io_port)
......
// SPDX-License-Identifier: GPL-2.0
//
// Register map access API - Memory region with raw access
//
// This is intended for testing only
//
// Copyright (c) 2023, Arm Ltd
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/swab.h>
#include "internal.h"
static unsigned int decode_reg(enum regmap_endian endian, const void *reg)
{
const u16 *r = reg;
if (endian == REGMAP_ENDIAN_BIG)
return be16_to_cpu(*r);
else
return le16_to_cpu(*r);
}
static int regmap_raw_ram_gather_write(void *context,
const void *reg, size_t reg_len,
const void *val, size_t val_len)
{
struct regmap_ram_data *data = context;
unsigned int r;
u16 *our_buf = (u16 *)data->vals;
int i;
if (reg_len != 2)
return -EINVAL;
if (val_len % 2)
return -EINVAL;
r = decode_reg(data->reg_endian, reg);
memcpy(&our_buf[r], val, val_len);
for (i = 0; i < val_len / 2; i++)
data->written[r + i] = true;
return 0;
}
static int regmap_raw_ram_write(void *context, const void *data, size_t count)
{
return regmap_raw_ram_gather_write(context, data, 2,
data + 2, count - 2);
}
static int regmap_raw_ram_read(void *context,
const void *reg, size_t reg_len,
void *val, size_t val_len)
{
struct regmap_ram_data *data = context;
unsigned int r;
u16 *our_buf = (u16 *)data->vals;
int i;
if (reg_len != 2)
return -EINVAL;
if (val_len % 2)
return -EINVAL;
r = decode_reg(data->reg_endian, reg);
memcpy(val, &our_buf[r], val_len);
for (i = 0; i < val_len / 2; i++)
data->read[r + i] = true;
return 0;
}
static void regmap_raw_ram_free_context(void *context)
{
struct regmap_ram_data *data = context;
kfree(data->vals);
kfree(data->read);
kfree(data->written);
kfree(data);
}
static const struct regmap_bus regmap_raw_ram = {
.fast_io = true,
.write = regmap_raw_ram_write,
.gather_write = regmap_raw_ram_gather_write,
.read = regmap_raw_ram_read,
.free_context = regmap_raw_ram_free_context,
};
struct regmap *__regmap_init_raw_ram(const struct regmap_config *config,
struct regmap_ram_data *data,
struct lock_class_key *lock_key,
const char *lock_name)
{
struct regmap *map;
if (config->reg_bits != 16)
return ERR_PTR(-EINVAL);
if (!config->max_register) {
pr_crit("No max_register specified for RAM regmap\n");
return ERR_PTR(-EINVAL);
}
data->read = kcalloc(sizeof(bool), config->max_register + 1,
GFP_KERNEL);
if (!data->read)
return ERR_PTR(-ENOMEM);
data->written = kcalloc(sizeof(bool), config->max_register + 1,
GFP_KERNEL);
if (!data->written)
return ERR_PTR(-ENOMEM);
data->reg_endian = config->reg_format_endian;
map = __regmap_init(NULL, &regmap_raw_ram, data, config,
lock_key, lock_name);
return map;
}
EXPORT_SYMBOL_GPL(__regmap_init_raw_ram);
MODULE_LICENSE("GPL v2");
......@@ -2983,6 +2983,11 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val,
size_t chunk_count, chunk_bytes;
size_t chunk_regs = val_count;
if (!map->cache_bypass && map->cache_only) {
ret = -EBUSY;
goto out;
}
if (!map->read) {
ret = -ENOTSUPP;
goto out;
......@@ -3078,18 +3083,19 @@ int regmap_noinc_read(struct regmap *map, unsigned int reg,
goto out_unlock;
}
/* Use the accelerated operation if we can */
if (map->bus->reg_noinc_read) {
/*
* We have not defined the FIFO semantics for cache, as the
* cache is just one value deep. Should we return the last
* written value? Just avoid this by always reading the FIFO
* even when using cache. Cache only will not work.
*/
if (map->cache_only) {
if (!map->cache_bypass && map->cache_only) {
ret = -EBUSY;
goto out_unlock;
}
/* Use the accelerated operation if we can */
if (map->bus->reg_noinc_read) {
ret = regmap_noinc_readwrite(map, reg, val, val_len, false);
goto out_unlock;
}
......@@ -3273,7 +3279,7 @@ static int _regmap_update_bits(struct regmap *map, unsigned int reg,
tmp = orig & ~mask;
tmp |= val & mask;
if (force_write || (tmp != orig)) {
if (force_write || (tmp != orig) || map->force_write_field) {
ret = _regmap_write(map, reg, tmp);
if (ret == 0 && change)
*change = true;
......
......@@ -100,13 +100,23 @@ static const struct regmap_irq dio48e_regmap_irqs[] = {
DIO48E_REGMAP_IRQ(0), DIO48E_REGMAP_IRQ(1),
};
static int dio48e_handle_mask_sync(struct regmap *const map, const int index,
/**
* struct dio48e_gpio - GPIO device private data structure
* @map: Regmap for the device
* @irq_mask: Current IRQ mask state on the device
*/
struct dio48e_gpio {
struct regmap *map;
unsigned int irq_mask;
};
static int dio48e_handle_mask_sync(const int index,
const unsigned int mask_buf_def,
const unsigned int mask_buf,
void *const irq_drv_data)
{
unsigned int *const irq_mask = irq_drv_data;
const unsigned int prev_mask = *irq_mask;
struct dio48e_gpio *const dio48egpio = irq_drv_data;
const unsigned int prev_mask = dio48egpio->irq_mask;
int err;
unsigned int val;
......@@ -115,19 +125,19 @@ static int dio48e_handle_mask_sync(struct regmap *const map, const int index,
return 0;
/* remember the current mask for the next mask sync */
*irq_mask = mask_buf;
dio48egpio->irq_mask = mask_buf;
/* if all previously masked, enable interrupts when unmasking */
if (prev_mask == mask_buf_def) {
err = regmap_write(map, DIO48E_CLEAR_INTERRUPT, 0x00);
err = regmap_write(dio48egpio->map, DIO48E_CLEAR_INTERRUPT, 0x00);
if (err)
return err;
return regmap_write(map, DIO48E_ENABLE_INTERRUPT, 0x00);
return regmap_write(dio48egpio->map, DIO48E_ENABLE_INTERRUPT, 0x00);
}
/* if all are currently masked, disable interrupts */
if (mask_buf == mask_buf_def)
return regmap_read(map, DIO48E_DISABLE_INTERRUPT, &val);
return regmap_read(dio48egpio->map, DIO48E_DISABLE_INTERRUPT, &val);
return 0;
}
......@@ -168,7 +178,7 @@ static int dio48e_probe(struct device *dev, unsigned int id)
struct regmap *map;
int err;
struct regmap_irq_chip *chip;
unsigned int irq_mask;
struct dio48e_gpio *dio48egpio;
struct regmap_irq_chip_data *chip_data;
if (!devm_request_region(dev, base[id], DIO48E_EXTENT, name)) {
......@@ -186,12 +196,14 @@ static int dio48e_probe(struct device *dev, unsigned int id)
return dev_err_probe(dev, PTR_ERR(map),
"Unable to initialize register map\n");
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
dio48egpio = devm_kzalloc(dev, sizeof(*dio48egpio), GFP_KERNEL);
if (!dio48egpio)
return -ENOMEM;
chip->irq_drv_data = devm_kzalloc(dev, sizeof(irq_mask), GFP_KERNEL);
if (!chip->irq_drv_data)
dio48egpio->map = map;
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->name = name;
......@@ -202,6 +214,7 @@ static int dio48e_probe(struct device *dev, unsigned int id)
chip->irqs = dio48e_regmap_irqs;
chip->num_irqs = ARRAY_SIZE(dio48e_regmap_irqs);
chip->handle_mask_sync = dio48e_handle_mask_sync;
chip->irq_drv_data = dio48egpio;
/* Initialize to prevent spurious interrupts before we're ready */
err = dio48e_irq_init_hw(map);
......
......@@ -1528,9 +1528,6 @@ struct regmap_irq_chip_data;
* status_base. Should contain num_regs arrays.
* Can be provided for chips with more complex mapping than
* 1.st bit to 1.st sub-reg, 2.nd bit to 2.nd sub-reg, ...
* When used with not_fixed_stride, each one-element array
* member contains offset calculated as address from each
* peripheral to first peripheral.
* @num_main_regs: Number of 'main status' irq registers for chips which have
* main_status set.
*
......@@ -1542,10 +1539,6 @@ struct regmap_irq_chip_data;
* @ack_base: Base ack address. If zero then the chip is clear on read.
* Using zero value is possible with @use_ack bit.
* @wake_base: Base address for wake enables. If zero unsupported.
* @type_base: Base address for irq type. If zero unsupported. Deprecated,
* use @config_base instead.
* @virt_reg_base: Base addresses for extra config regs. Deprecated, use
* @config_base instead.
* @config_base: Base address for IRQ type config regs. If null unsupported.
* @irq_reg_stride: Stride to use for chips where registers are not contiguous.
* @init_ack_masked: Ack all masked interrupts once during initalization.
......@@ -1571,11 +1564,6 @@ struct regmap_irq_chip_data;
* registers before unmasking interrupts to clear any bits
* set when they were masked.
* @runtime_pm: Hold a runtime PM lock on the device when accessing it.
* @not_fixed_stride: Used when chip peripherals are not laid out with fixed
* stride. Must be used with sub_reg_offsets containing the
* offsets to each peripheral. Deprecated; the same thing
* can be accomplished with a @get_irq_reg callback, without
* the need for a @sub_reg_offsets table.
* @no_status: No status register: all interrupts assumed generated by device.
*
* @num_regs: Number of registers in each control bank.
......@@ -1583,12 +1571,6 @@ struct regmap_irq_chip_data;
* @irqs: Descriptors for individual IRQs. Interrupt numbers are
* assigned based on the index in the array of the interrupt.
* @num_irqs: Number of descriptors.
*
* @num_type_reg: Number of type registers. Deprecated, use config registers
* instead.
* @num_virt_regs: Number of non-standard irq configuration registers.
* If zero unsupported. Deprecated, use config registers
* instead.
* @num_config_bases: Number of config base registers.
* @num_config_regs: Number of config registers for each config base register.
*
......@@ -1598,15 +1580,12 @@ struct regmap_irq_chip_data;
* after handling the interrupts in regmap_irq_handler().
* @handle_mask_sync: Callback used to handle IRQ mask syncs. The index will be
* in the range [0, num_regs)
* @set_type_virt: Driver specific callback to extend regmap_irq_set_type()
* and configure virt regs. Deprecated, use @set_type_config
* callback and config registers instead.
* @set_type_config: Callback used for configuring irq types.
* @get_irq_reg: Callback for mapping (base register, index) pairs to register
* addresses. The base register will be one of @status_base,
* @mask_base, etc., @main_status, or any of @config_base.
* The index will be in the range [0, num_main_regs[ for the
* main status base, [0, num_type_settings[ for any config
* main status base, [0, num_config_regs[ for any config
* register base, and [0, num_regs[ for any other base.
* If unspecified then regmap_irq_get_irq_reg_linear() is used.
* @irq_drv_data: Driver specific IRQ data which is passed as parameter when
......@@ -1629,8 +1608,6 @@ struct regmap_irq_chip {
unsigned int unmask_base;
unsigned int ack_base;
unsigned int wake_base;
unsigned int type_base;
unsigned int *virt_reg_base;
const unsigned int *config_base;
unsigned int irq_reg_stride;
unsigned int init_ack_masked:1;
......@@ -1643,7 +1620,6 @@ struct regmap_irq_chip {
unsigned int type_in_mask:1;
unsigned int clear_on_unmask:1;
unsigned int runtime_pm:1;
unsigned int not_fixed_stride:1;
unsigned int no_status:1;
int num_regs;
......@@ -1651,18 +1627,13 @@ struct regmap_irq_chip {
const struct regmap_irq *irqs;
int num_irqs;
int num_type_reg;
int num_virt_regs;
int num_config_bases;
int num_config_regs;
int (*handle_pre_irq)(void *irq_drv_data);
int (*handle_post_irq)(void *irq_drv_data);
int (*handle_mask_sync)(struct regmap *map, int index,
unsigned int mask_buf_def,
int (*handle_mask_sync)(int index, unsigned int mask_buf_def,
unsigned int mask_buf, void *irq_drv_data);
int (*set_type_virt)(unsigned int **buf, unsigned int type,
unsigned long hwirq, int reg);
int (*set_type_config)(unsigned int **buf, unsigned int type,
const struct regmap_irq *irq_data, int idx,
void *irq_drv_data);
......
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