Commit 68a25c36 authored by Bartosz Golaszewski's avatar Bartosz Golaszewski

Merge tag 'intel-gpio-v6.10-1' of...

Merge tag 'intel-gpio-v6.10-1' of git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel into gpio/for-next

intel-gpio for v6.10-1

* New driver for vGPIO controller on Intel Granite Rapids-D
* Update ACPI GPIO library to unify the IRQ code path
* Better GPIO IRQ line labeling for ACPI
* Switched Intel SCH driver to use "mapped" I/O accessors

The following is an automated git shortlog grouped by driver:

Add Intel Granite Rapids-D vGPIO driver:
 - Add Intel Granite Rapids-D vGPIO driver

crystalcove:
 -  Use -ENOTSUPP consistently

gpiolib:
 -  acpi: Set label for IRQ only lines
 -  acpi: Add fwnode name to the GPIO interrupt label
 -  acpi: Pass con_id instead of property into acpi_dev_gpio_irq_get_by()
 -  acpi: Move acpi_can_fallback_to_crs() out of __acpi_find_gpio()
 -  acpi: Simplify error handling in __acpi_find_gpio()
 -  acpi: Extract __acpi_find_gpio() helper
 -  acpi: Check for errors first in acpi_find_gpio()
 -  acpi: Remove never true check in acpi_get_gpiod_by_index()

sch:
 -  Utilise temporary variable for struct device
 -  Switch to memory mapped IO accessors

wcove:
 -  Use -ENOTSUPP consistently
parents 5539287c ecc4b141
...@@ -10895,6 +10895,7 @@ L: linux-gpio@vger.kernel.org ...@@ -10895,6 +10895,7 @@ L: linux-gpio@vger.kernel.org
S: Supported S: Supported
T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel.git T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel.git
F: drivers/gpio/gpio-elkhartlake.c F: drivers/gpio/gpio-elkhartlake.c
F: drivers/gpio/gpio-graniterapids.c
F: drivers/gpio/gpio-ich.c F: drivers/gpio/gpio-ich.c
F: drivers/gpio/gpio-merrifield.c F: drivers/gpio/gpio-merrifield.c
F: drivers/gpio/gpio-ml-ioh.c F: drivers/gpio/gpio-ml-ioh.c
......
...@@ -312,6 +312,24 @@ config GPIO_GENERIC_PLATFORM ...@@ -312,6 +312,24 @@ config GPIO_GENERIC_PLATFORM
help help
Say yes here to support basic platform_device memory-mapped GPIO controllers. Say yes here to support basic platform_device memory-mapped GPIO controllers.
config GPIO_GRANITERAPIDS
tristate "Intel Granite Rapids-D vGPIO support"
depends on X86 || COMPILE_TEST
select GPIOLIB_IRQCHIP
help
Select this to enable virtual GPIO support on platforms with the
following SoCs:
- Intel Granite Rapids-D
The driver enables basic GPIO functionality and implements interrupt
support. The virtual GPIO driver controls GPIO lines via a firmware
interface. The physical GPIO pins reside on device that is external
from the main SoC package, such as a BMC or a CPLD.
To compile this driver as a module, choose M here: the module will
be called gpio-graniterapids.
config GPIO_GRGPIO config GPIO_GRGPIO
tristate "Aeroflex Gaisler GRGPIO support" tristate "Aeroflex Gaisler GRGPIO support"
depends on OF_GPIO depends on OF_GPIO
......
...@@ -66,6 +66,7 @@ obj-$(CONFIG_GPIO_FTGPIO010) += gpio-ftgpio010.o ...@@ -66,6 +66,7 @@ obj-$(CONFIG_GPIO_FTGPIO010) += gpio-ftgpio010.o
obj-$(CONFIG_GPIO_FXL6408) += gpio-fxl6408.o obj-$(CONFIG_GPIO_FXL6408) += gpio-fxl6408.o
obj-$(CONFIG_GPIO_GE_FPGA) += gpio-ge.o obj-$(CONFIG_GPIO_GE_FPGA) += gpio-ge.o
obj-$(CONFIG_GPIO_GPIO_MM) += gpio-gpio-mm.o obj-$(CONFIG_GPIO_GPIO_MM) += gpio-gpio-mm.o
obj-$(CONFIG_GPIO_GRANITERAPIDS) += gpio-graniterapids.o
obj-$(CONFIG_GPIO_GRGPIO) += gpio-grgpio.o obj-$(CONFIG_GPIO_GRGPIO) += gpio-grgpio.o
obj-$(CONFIG_GPIO_GW_PLD) += gpio-gw-pld.o obj-$(CONFIG_GPIO_GW_PLD) += gpio-gw-pld.o
obj-$(CONFIG_GPIO_HISI) += gpio-hisi.o obj-$(CONFIG_GPIO_HISI) += gpio-hisi.o
......
...@@ -92,7 +92,7 @@ static inline int to_reg(int gpio, enum ctrl_register reg_type) ...@@ -92,7 +92,7 @@ static inline int to_reg(int gpio, enum ctrl_register reg_type)
case 0x5e: case 0x5e:
return GPIOPANELCTL; return GPIOPANELCTL;
default: default:
return -EOPNOTSUPP; return -ENOTSUPP;
} }
} }
......
This diff is collapsed.
...@@ -144,7 +144,7 @@ static int pca953x_acpi_get_irq(struct device *dev) ...@@ -144,7 +144,7 @@ static int pca953x_acpi_get_irq(struct device *dev)
if (ret) if (ret)
dev_warn(dev, "can't add GPIO ACPI mapping\n"); dev_warn(dev, "can't add GPIO ACPI mapping\n");
ret = acpi_dev_gpio_irq_get_by(ACPI_COMPANION(dev), "irq-gpios", 0); ret = acpi_dev_gpio_irq_get_by(ACPI_COMPANION(dev), "irq", 0);
if (ret < 0) if (ret < 0)
return ret; return ret;
......
...@@ -38,8 +38,8 @@ ...@@ -38,8 +38,8 @@
struct sch_gpio { struct sch_gpio {
struct gpio_chip chip; struct gpio_chip chip;
void __iomem *regs;
spinlock_t lock; spinlock_t lock;
unsigned short iobase;
unsigned short resume_base; unsigned short resume_base;
/* GPE handling */ /* GPE handling */
...@@ -75,7 +75,7 @@ static int sch_gpio_reg_get(struct sch_gpio *sch, unsigned int gpio, unsigned in ...@@ -75,7 +75,7 @@ static int sch_gpio_reg_get(struct sch_gpio *sch, unsigned int gpio, unsigned in
offset = sch_gpio_offset(sch, gpio, reg); offset = sch_gpio_offset(sch, gpio, reg);
bit = sch_gpio_bit(sch, gpio); bit = sch_gpio_bit(sch, gpio);
reg_val = !!(inb(sch->iobase + offset) & BIT(bit)); reg_val = !!(ioread8(sch->regs + offset) & BIT(bit));
return reg_val; return reg_val;
} }
...@@ -89,12 +89,14 @@ static void sch_gpio_reg_set(struct sch_gpio *sch, unsigned int gpio, unsigned i ...@@ -89,12 +89,14 @@ static void sch_gpio_reg_set(struct sch_gpio *sch, unsigned int gpio, unsigned i
offset = sch_gpio_offset(sch, gpio, reg); offset = sch_gpio_offset(sch, gpio, reg);
bit = sch_gpio_bit(sch, gpio); bit = sch_gpio_bit(sch, gpio);
reg_val = inb(sch->iobase + offset); reg_val = ioread8(sch->regs + offset);
if (val) if (val)
outb(reg_val | BIT(bit), sch->iobase + offset); reg_val |= BIT(bit);
else else
outb((reg_val & ~BIT(bit)), sch->iobase + offset); reg_val &= ~BIT(bit);
iowrite8(reg_val, sch->regs + offset);
} }
static int sch_gpio_direction_in(struct gpio_chip *gc, unsigned int gpio_num) static int sch_gpio_direction_in(struct gpio_chip *gc, unsigned int gpio_num)
...@@ -267,8 +269,8 @@ static u32 sch_gpio_gpe_handler(acpi_handle gpe_device, u32 gpe, void *context) ...@@ -267,8 +269,8 @@ static u32 sch_gpio_gpe_handler(acpi_handle gpe_device, u32 gpe, void *context)
spin_lock_irqsave(&sch->lock, flags); spin_lock_irqsave(&sch->lock, flags);
core_status = inl(sch->iobase + CORE_BANK_OFFSET + GTS); core_status = ioread32(sch->regs + CORE_BANK_OFFSET + GTS);
resume_status = inl(sch->iobase + RESUME_BANK_OFFSET + GTS); resume_status = ioread32(sch->regs + RESUME_BANK_OFFSET + GTS);
spin_unlock_irqrestore(&sch->lock, flags); spin_unlock_irqrestore(&sch->lock, flags);
...@@ -319,12 +321,14 @@ static int sch_gpio_install_gpe_handler(struct sch_gpio *sch) ...@@ -319,12 +321,14 @@ static int sch_gpio_install_gpe_handler(struct sch_gpio *sch)
static int sch_gpio_probe(struct platform_device *pdev) static int sch_gpio_probe(struct platform_device *pdev)
{ {
struct device *dev = &pdev->dev;
struct gpio_irq_chip *girq; struct gpio_irq_chip *girq;
struct sch_gpio *sch; struct sch_gpio *sch;
struct resource *res; struct resource *res;
void __iomem *regs;
int ret; int ret;
sch = devm_kzalloc(&pdev->dev, sizeof(*sch), GFP_KERNEL); sch = devm_kzalloc(dev, sizeof(*sch), GFP_KERNEL);
if (!sch) if (!sch)
return -ENOMEM; return -ENOMEM;
...@@ -332,15 +336,16 @@ static int sch_gpio_probe(struct platform_device *pdev) ...@@ -332,15 +336,16 @@ static int sch_gpio_probe(struct platform_device *pdev)
if (!res) if (!res)
return -EBUSY; return -EBUSY;
if (!devm_request_region(&pdev->dev, res->start, resource_size(res), regs = devm_ioport_map(dev, res->start, resource_size(res));
pdev->name)) if (!regs)
return -EBUSY; return -EBUSY;
sch->regs = regs;
spin_lock_init(&sch->lock); spin_lock_init(&sch->lock);
sch->iobase = res->start;
sch->chip = sch_gpio_chip; sch->chip = sch_gpio_chip;
sch->chip.label = dev_name(&pdev->dev); sch->chip.label = dev_name(dev);
sch->chip.parent = &pdev->dev; sch->chip.parent = dev;
switch (pdev->id) { switch (pdev->id) {
case PCI_DEVICE_ID_INTEL_SCH_LPC: case PCI_DEVICE_ID_INTEL_SCH_LPC:
...@@ -394,9 +399,9 @@ static int sch_gpio_probe(struct platform_device *pdev) ...@@ -394,9 +399,9 @@ static int sch_gpio_probe(struct platform_device *pdev)
ret = sch_gpio_install_gpe_handler(sch); ret = sch_gpio_install_gpe_handler(sch);
if (ret) if (ret)
dev_warn(&pdev->dev, "Can't setup GPE, no IRQ support\n"); dev_warn(dev, "Can't setup GPE, no IRQ support\n");
return devm_gpiochip_add_data(&pdev->dev, &sch->chip, sch); return devm_gpiochip_add_data(dev, &sch->chip, sch);
} }
static struct platform_driver sch_gpio_driver = { static struct platform_driver sch_gpio_driver = {
......
...@@ -104,7 +104,7 @@ static inline int to_reg(int gpio, enum ctrl_register type) ...@@ -104,7 +104,7 @@ static inline int to_reg(int gpio, enum ctrl_register type)
unsigned int reg = type == CTRL_IN ? GPIO_IN_CTRL_BASE : GPIO_OUT_CTRL_BASE; unsigned int reg = type == CTRL_IN ? GPIO_IN_CTRL_BASE : GPIO_OUT_CTRL_BASE;
if (gpio >= WCOVE_GPIO_NUM) if (gpio >= WCOVE_GPIO_NUM)
return -EOPNOTSUPP; return -ENOTSUPP;
return reg + gpio; return reg + gpio;
} }
......
...@@ -873,9 +873,6 @@ static struct gpio_desc *acpi_get_gpiod_by_index(struct acpi_device *adev, ...@@ -873,9 +873,6 @@ static struct gpio_desc *acpi_get_gpiod_by_index(struct acpi_device *adev,
struct acpi_gpio_lookup lookup; struct acpi_gpio_lookup lookup;
int ret; int ret;
if (!adev)
return ERR_PTR(-ENODEV);
memset(&lookup, 0, sizeof(lookup)); memset(&lookup, 0, sizeof(lookup));
lookup.index = index; lookup.index = index;
...@@ -948,14 +945,11 @@ static bool acpi_can_fallback_to_crs(struct acpi_device *adev, ...@@ -948,14 +945,11 @@ static bool acpi_can_fallback_to_crs(struct acpi_device *adev,
return con_id == NULL; return con_id == NULL;
} }
struct gpio_desc *acpi_find_gpio(struct fwnode_handle *fwnode, static struct gpio_desc *
const char *con_id, __acpi_find_gpio(struct fwnode_handle *fwnode, const char *con_id, unsigned int idx,
unsigned int idx, bool can_fallback, struct acpi_gpio_info *info)
enum gpiod_flags *dflags,
unsigned long *lookupflags)
{ {
struct acpi_device *adev = to_acpi_device_node(fwnode); struct acpi_device *adev = to_acpi_device_node(fwnode);
struct acpi_gpio_info info;
struct gpio_desc *desc; struct gpio_desc *desc;
char propname[32]; char propname[32];
int i; int i;
...@@ -972,25 +966,38 @@ struct gpio_desc *acpi_find_gpio(struct fwnode_handle *fwnode, ...@@ -972,25 +966,38 @@ struct gpio_desc *acpi_find_gpio(struct fwnode_handle *fwnode,
if (adev) if (adev)
desc = acpi_get_gpiod_by_index(adev, desc = acpi_get_gpiod_by_index(adev,
propname, idx, &info); propname, idx, info);
else else
desc = acpi_get_gpiod_from_data(fwnode, desc = acpi_get_gpiod_from_data(fwnode,
propname, idx, &info); propname, idx, info);
if (!IS_ERR(desc))
break;
if (PTR_ERR(desc) == -EPROBE_DEFER) if (PTR_ERR(desc) == -EPROBE_DEFER)
return ERR_CAST(desc); return ERR_CAST(desc);
if (!IS_ERR(desc))
return desc;
} }
/* Then from plain _CRS GPIOs */ /* Then from plain _CRS GPIOs */
if (IS_ERR(desc)) { if (!adev || !can_fallback)
if (!adev || !acpi_can_fallback_to_crs(adev, con_id)) return ERR_PTR(-ENOENT);
return ERR_PTR(-ENOENT);
desc = acpi_get_gpiod_by_index(adev, NULL, idx, &info); return acpi_get_gpiod_by_index(adev, NULL, idx, info);
if (IS_ERR(desc)) }
return desc;
} struct gpio_desc *acpi_find_gpio(struct fwnode_handle *fwnode,
const char *con_id,
unsigned int idx,
enum gpiod_flags *dflags,
unsigned long *lookupflags)
{
struct acpi_device *adev = to_acpi_device_node(fwnode);
bool can_fallback = acpi_can_fallback_to_crs(adev, con_id);
struct acpi_gpio_info info;
struct gpio_desc *desc;
desc = __acpi_find_gpio(fwnode, con_id, idx, can_fallback, &info);
if (IS_ERR(desc))
return desc;
if (info.gpioint && if (info.gpioint &&
(*dflags == GPIOD_OUT_LOW || *dflags == GPIOD_OUT_HIGH)) { (*dflags == GPIOD_OUT_LOW || *dflags == GPIOD_OUT_HIGH)) {
...@@ -1006,7 +1013,7 @@ struct gpio_desc *acpi_find_gpio(struct fwnode_handle *fwnode, ...@@ -1006,7 +1013,7 @@ struct gpio_desc *acpi_find_gpio(struct fwnode_handle *fwnode,
/** /**
* acpi_dev_gpio_irq_wake_get_by() - Find GpioInt and translate it to Linux IRQ number * acpi_dev_gpio_irq_wake_get_by() - Find GpioInt and translate it to Linux IRQ number
* @adev: pointer to a ACPI device to get IRQ from * @adev: pointer to a ACPI device to get IRQ from
* @name: optional name of GpioInt resource * @con_id: optional name of GpioInt resource
* @index: index of GpioInt resource (starting from %0) * @index: index of GpioInt resource (starting from %0)
* @wake_capable: Set to true if the IRQ is wake capable * @wake_capable: Set to true if the IRQ is wake capable
* *
...@@ -1017,17 +1024,18 @@ struct gpio_desc *acpi_find_gpio(struct fwnode_handle *fwnode, ...@@ -1017,17 +1024,18 @@ struct gpio_desc *acpi_find_gpio(struct fwnode_handle *fwnode,
* The function is idempotent, though each time it runs it will configure GPIO * The function is idempotent, though each time it runs it will configure GPIO
* pin direction according to the flags in GpioInt resource. * pin direction according to the flags in GpioInt resource.
* *
* The function takes optional @name parameter. If the resource has a property * The function takes optional @con_id parameter. If the resource has
* name, then only those will be taken into account. * a @con_id in a property, then only those will be taken into account.
* *
* The GPIO is considered wake capable if the GpioInt resource specifies * The GPIO is considered wake capable if the GpioInt resource specifies
* SharedAndWake or ExclusiveAndWake. * SharedAndWake or ExclusiveAndWake.
* *
* Return: Linux IRQ number (> %0) on success, negative errno on failure. * Return: Linux IRQ number (> %0) on success, negative errno on failure.
*/ */
int acpi_dev_gpio_irq_wake_get_by(struct acpi_device *adev, const char *name, int index, int acpi_dev_gpio_irq_wake_get_by(struct acpi_device *adev, const char *con_id, int index,
bool *wake_capable) bool *wake_capable)
{ {
struct fwnode_handle *fwnode = acpi_fwnode_handle(adev);
int idx, i; int idx, i;
unsigned int irq_flags; unsigned int irq_flags;
int ret; int ret;
...@@ -1036,9 +1044,8 @@ int acpi_dev_gpio_irq_wake_get_by(struct acpi_device *adev, const char *name, in ...@@ -1036,9 +1044,8 @@ int acpi_dev_gpio_irq_wake_get_by(struct acpi_device *adev, const char *name, in
struct acpi_gpio_info info; struct acpi_gpio_info info;
struct gpio_desc *desc; struct gpio_desc *desc;
desc = acpi_get_gpiod_by_index(adev, name, i, &info);
/* Ignore -EPROBE_DEFER, it only matters if idx matches */ /* Ignore -EPROBE_DEFER, it only matters if idx matches */
desc = __acpi_find_gpio(fwnode, con_id, i, true, &info);
if (IS_ERR(desc) && PTR_ERR(desc) != -EPROBE_DEFER) if (IS_ERR(desc) && PTR_ERR(desc) != -EPROBE_DEFER)
return PTR_ERR(desc); return PTR_ERR(desc);
...@@ -1058,7 +1065,11 @@ int acpi_dev_gpio_irq_wake_get_by(struct acpi_device *adev, const char *name, in ...@@ -1058,7 +1065,11 @@ int acpi_dev_gpio_irq_wake_get_by(struct acpi_device *adev, const char *name, in
acpi_gpio_update_gpiod_flags(&dflags, &info); acpi_gpio_update_gpiod_flags(&dflags, &info);
acpi_gpio_update_gpiod_lookup_flags(&lflags, &info); acpi_gpio_update_gpiod_lookup_flags(&lflags, &info);
snprintf(label, sizeof(label), "GpioInt() %d", index); snprintf(label, sizeof(label), "%pfwP GpioInt(%d)", fwnode, index);
ret = gpiod_set_consumer_name(desc, con_id ?: label);
if (ret)
return ret;
ret = gpiod_configure_flags(desc, label, lflags, dflags); ret = gpiod_configure_flags(desc, label, lflags, dflags);
if (ret < 0) if (ret < 0)
return ret; return ret;
......
...@@ -435,7 +435,7 @@ static int mlxbf_gige_probe(struct platform_device *pdev) ...@@ -435,7 +435,7 @@ static int mlxbf_gige_probe(struct platform_device *pdev)
priv->rx_irq = platform_get_irq(pdev, MLXBF_GIGE_RECEIVE_PKT_INTR_IDX); priv->rx_irq = platform_get_irq(pdev, MLXBF_GIGE_RECEIVE_PKT_INTR_IDX);
priv->llu_plu_irq = platform_get_irq(pdev, MLXBF_GIGE_LLU_PLU_INTR_IDX); priv->llu_plu_irq = platform_get_irq(pdev, MLXBF_GIGE_LLU_PLU_INTR_IDX);
phy_irq = acpi_dev_gpio_irq_get_by(ACPI_COMPANION(&pdev->dev), "phy-gpios", 0); phy_irq = acpi_dev_gpio_irq_get_by(ACPI_COMPANION(&pdev->dev), "phy", 0);
if (phy_irq < 0) { if (phy_irq < 0) {
dev_err(&pdev->dev, "Error getting PHY irq. Use polling instead"); dev_err(&pdev->dev, "Error getting PHY irq. Use polling instead");
phy_irq = PHY_POLL; phy_irq = PHY_POLL;
......
...@@ -95,7 +95,7 @@ static int cy8c95x0_acpi_get_irq(struct device *dev) ...@@ -95,7 +95,7 @@ static int cy8c95x0_acpi_get_irq(struct device *dev)
if (ret) if (ret)
dev_warn(dev, "can't add GPIO ACPI mapping\n"); dev_warn(dev, "can't add GPIO ACPI mapping\n");
ret = acpi_dev_gpio_irq_get_by(ACPI_COMPANION(dev), "irq-gpios", 0); ret = acpi_dev_gpio_irq_get_by(ACPI_COMPANION(dev), "irq", 0);
if (ret < 0) if (ret < 0)
return ret; return ret;
......
...@@ -1233,7 +1233,7 @@ bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, ...@@ -1233,7 +1233,7 @@ bool acpi_gpio_get_irq_resource(struct acpi_resource *ares,
struct acpi_resource_gpio **agpio); struct acpi_resource_gpio **agpio);
bool acpi_gpio_get_io_resource(struct acpi_resource *ares, bool acpi_gpio_get_io_resource(struct acpi_resource *ares,
struct acpi_resource_gpio **agpio); struct acpi_resource_gpio **agpio);
int acpi_dev_gpio_irq_wake_get_by(struct acpi_device *adev, const char *name, int index, int acpi_dev_gpio_irq_wake_get_by(struct acpi_device *adev, const char *con_id, int index,
bool *wake_capable); bool *wake_capable);
#else #else
static inline bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, static inline bool acpi_gpio_get_irq_resource(struct acpi_resource *ares,
...@@ -1246,7 +1246,7 @@ static inline bool acpi_gpio_get_io_resource(struct acpi_resource *ares, ...@@ -1246,7 +1246,7 @@ static inline bool acpi_gpio_get_io_resource(struct acpi_resource *ares,
{ {
return false; return false;
} }
static inline int acpi_dev_gpio_irq_wake_get_by(struct acpi_device *adev, const char *name, static inline int acpi_dev_gpio_irq_wake_get_by(struct acpi_device *adev, const char *con_id,
int index, bool *wake_capable) int index, bool *wake_capable)
{ {
return -ENXIO; return -ENXIO;
...@@ -1259,10 +1259,10 @@ static inline int acpi_dev_gpio_irq_wake_get(struct acpi_device *adev, int index ...@@ -1259,10 +1259,10 @@ static inline int acpi_dev_gpio_irq_wake_get(struct acpi_device *adev, int index
return acpi_dev_gpio_irq_wake_get_by(adev, NULL, index, wake_capable); return acpi_dev_gpio_irq_wake_get_by(adev, NULL, index, wake_capable);
} }
static inline int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, const char *name, static inline int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, const char *con_id,
int index) int index)
{ {
return acpi_dev_gpio_irq_wake_get_by(adev, name, index, NULL); return acpi_dev_gpio_irq_wake_get_by(adev, con_id, index, NULL);
} }
static inline int acpi_dev_gpio_irq_get(struct acpi_device *adev, int index) static inline int acpi_dev_gpio_irq_get(struct acpi_device *adev, int index)
......
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