Commit e88481f7 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'rproc-v6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/remoteproc/linux

Pull remoteproc updates from Bjorn Andersson:

 - The i.MX DSP remoteproc driver adds support for providing a resource
   table, in order to enable IPC with the core

 - The TI K3 DSP driver is transitioned to remove_new, error messages
   are changed to use symbolic error codes, and dev_err_probe() is used
   where applicable

 - Support for the Qualcomm SC7280 audio, compute and WiFi co-processors
   are added to the Qualcomm TrustZone based remoteproc driver

* tag 'rproc-v6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/remoteproc/linux:
  remoteproc: qcom_q6v5_pas: Add SC7280 ADSP, CDSP & WPSS
  dt-bindings: remoteproc: qcom: sc7180-pas: Add SC7280 compatibles
  dt-bindings: remoteproc: qcom: sc7180-pas: Fix SC7280 MPSS PD-names
  remoteproc: k3-dsp: Convert to platform remove callback returning void
  remoteproc: k3-dsp: Use symbolic error codes in error messages
  remoteproc: k3-dsp: Suppress duplicate error message in .remove()
  arm64: dts: imx8mp: Add reserve-memory nodes for DSP
  remoteproc: imx_dsp_rproc: Add mandatory find_loaded_rsc_table op
parents 2a434346 300ed425
...@@ -18,7 +18,10 @@ properties: ...@@ -18,7 +18,10 @@ properties:
enum: enum:
- qcom,sc7180-adsp-pas - qcom,sc7180-adsp-pas
- qcom,sc7180-mpss-pas - qcom,sc7180-mpss-pas
- qcom,sc7280-adsp-pas
- qcom,sc7280-cdsp-pas
- qcom,sc7280-mpss-pas - qcom,sc7280-mpss-pas
- qcom,sc7280-wpss-pas
reg: reg:
maxItems: 1 maxItems: 1
...@@ -75,6 +78,7 @@ allOf: ...@@ -75,6 +78,7 @@ allOf:
compatible: compatible:
enum: enum:
- qcom,sc7180-adsp-pas - qcom,sc7180-adsp-pas
- qcom,sc7280-adsp-pas
then: then:
properties: properties:
power-domains: power-domains:
...@@ -109,6 +113,23 @@ allOf: ...@@ -109,6 +113,23 @@ allOf:
compatible: compatible:
enum: enum:
- qcom,sc7280-mpss-pas - qcom,sc7280-mpss-pas
then:
properties:
power-domains:
items:
- description: CX power domain
- description: MSS power domain
power-domain-names:
items:
- const: cx
- const: mss
- if:
properties:
compatible:
enum:
- qcom,sc7280-cdsp-pas
- qcom,sc7280-wpss-pas
then: then:
properties: properties:
power-domains: power-domains:
......
...@@ -137,6 +137,28 @@ simple-audio-card,codec { ...@@ -137,6 +137,28 @@ simple-audio-card,codec {
}; };
}; };
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
dsp_vdev0vring0: vdev0vring0@942f0000 {
reg = <0 0x942f0000 0 0x8000>;
no-map;
};
dsp_vdev0vring1: vdev0vring1@942f8000 {
reg = <0 0x942f8000 0 0x8000>;
no-map;
};
dsp_vdev0buffer: vdev0buffer@94300000 {
compatible = "shared-dma-pool";
reg = <0 0x94300000 0 0x100000>;
no-map;
};
};
}; };
&flexspi { &flexspi {
......
...@@ -940,6 +940,7 @@ static const struct rproc_ops imx_dsp_rproc_ops = { ...@@ -940,6 +940,7 @@ static const struct rproc_ops imx_dsp_rproc_ops = {
.kick = imx_dsp_rproc_kick, .kick = imx_dsp_rproc_kick,
.load = imx_dsp_rproc_elf_load_segments, .load = imx_dsp_rproc_elf_load_segments,
.parse_fw = imx_dsp_rproc_parse_fw, .parse_fw = imx_dsp_rproc_parse_fw,
.find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table,
.sanity_check = rproc_elf_sanity_check, .sanity_check = rproc_elf_sanity_check,
.get_boot_addr = rproc_elf_get_boot_addr, .get_boot_addr = rproc_elf_get_boot_addr,
}; };
......
...@@ -1165,6 +1165,22 @@ static const struct adsp_data sm8550_mpss_resource = { ...@@ -1165,6 +1165,22 @@ static const struct adsp_data sm8550_mpss_resource = {
.region_assign_idx = 2, .region_assign_idx = 2,
}; };
static const struct adsp_data sc7280_wpss_resource = {
.crash_reason_smem = 626,
.firmware_name = "wpss.mdt",
.pas_id = 6,
.auto_boot = true,
.proxy_pd_names = (char*[]){
"cx",
"mx",
NULL
},
.load_state = "wpss",
.ssr_name = "wpss",
.sysmon_name = "wpss",
.ssctl_id = 0x19,
};
static const struct of_device_id adsp_of_match[] = { static const struct of_device_id adsp_of_match[] = {
{ .compatible = "qcom,msm8226-adsp-pil", .data = &adsp_resource_init}, { .compatible = "qcom,msm8226-adsp-pil", .data = &adsp_resource_init},
{ .compatible = "qcom,msm8953-adsp-pil", .data = &msm8996_adsp_resource}, { .compatible = "qcom,msm8953-adsp-pil", .data = &msm8996_adsp_resource},
...@@ -1178,7 +1194,10 @@ static const struct of_device_id adsp_of_match[] = { ...@@ -1178,7 +1194,10 @@ static const struct of_device_id adsp_of_match[] = {
{ .compatible = "qcom,qcs404-wcss-pas", .data = &wcss_resource_init }, { .compatible = "qcom,qcs404-wcss-pas", .data = &wcss_resource_init },
{ .compatible = "qcom,sc7180-adsp-pas", .data = &sm8250_adsp_resource}, { .compatible = "qcom,sc7180-adsp-pas", .data = &sm8250_adsp_resource},
{ .compatible = "qcom,sc7180-mpss-pas", .data = &mpss_resource_init}, { .compatible = "qcom,sc7180-mpss-pas", .data = &mpss_resource_init},
{ .compatible = "qcom,sc7280-adsp-pas", .data = &sm8350_adsp_resource},
{ .compatible = "qcom,sc7280-cdsp-pas", .data = &sm6350_cdsp_resource},
{ .compatible = "qcom,sc7280-mpss-pas", .data = &mpss_resource_init}, { .compatible = "qcom,sc7280-mpss-pas", .data = &mpss_resource_init},
{ .compatible = "qcom,sc7280-wpss-pas", .data = &sc7280_wpss_resource},
{ .compatible = "qcom,sc8180x-adsp-pas", .data = &sm8150_adsp_resource}, { .compatible = "qcom,sc8180x-adsp-pas", .data = &sm8150_adsp_resource},
{ .compatible = "qcom,sc8180x-cdsp-pas", .data = &sm8150_cdsp_resource}, { .compatible = "qcom,sc8180x-cdsp-pas", .data = &sm8150_cdsp_resource},
{ .compatible = "qcom,sc8180x-mpss-pas", .data = &sc8180x_mpss_resource}, { .compatible = "qcom,sc8180x-mpss-pas", .data = &sc8180x_mpss_resource},
......
...@@ -158,8 +158,8 @@ static void k3_dsp_rproc_kick(struct rproc *rproc, int vqid) ...@@ -158,8 +158,8 @@ static void k3_dsp_rproc_kick(struct rproc *rproc, int vqid)
/* send the index of the triggered virtqueue in the mailbox payload */ /* send the index of the triggered virtqueue in the mailbox payload */
ret = mbox_send_message(kproc->mbox, (void *)msg); ret = mbox_send_message(kproc->mbox, (void *)msg);
if (ret < 0) if (ret < 0)
dev_err(dev, "failed to send mailbox message, status = %d\n", dev_err(dev, "failed to send mailbox message (%pe)\n",
ret); ERR_PTR(ret));
} }
/* Put the DSP processor into reset */ /* Put the DSP processor into reset */
...@@ -170,7 +170,7 @@ static int k3_dsp_rproc_reset(struct k3_dsp_rproc *kproc) ...@@ -170,7 +170,7 @@ static int k3_dsp_rproc_reset(struct k3_dsp_rproc *kproc)
ret = reset_control_assert(kproc->reset); ret = reset_control_assert(kproc->reset);
if (ret) { if (ret) {
dev_err(dev, "local-reset assert failed, ret = %d\n", ret); dev_err(dev, "local-reset assert failed (%pe)\n", ERR_PTR(ret));
return ret; return ret;
} }
...@@ -180,7 +180,7 @@ static int k3_dsp_rproc_reset(struct k3_dsp_rproc *kproc) ...@@ -180,7 +180,7 @@ static int k3_dsp_rproc_reset(struct k3_dsp_rproc *kproc)
ret = kproc->ti_sci->ops.dev_ops.put_device(kproc->ti_sci, ret = kproc->ti_sci->ops.dev_ops.put_device(kproc->ti_sci,
kproc->ti_sci_id); kproc->ti_sci_id);
if (ret) { if (ret) {
dev_err(dev, "module-reset assert failed, ret = %d\n", ret); dev_err(dev, "module-reset assert failed (%pe)\n", ERR_PTR(ret));
if (reset_control_deassert(kproc->reset)) if (reset_control_deassert(kproc->reset))
dev_warn(dev, "local-reset deassert back failed\n"); dev_warn(dev, "local-reset deassert back failed\n");
} }
...@@ -200,14 +200,14 @@ static int k3_dsp_rproc_release(struct k3_dsp_rproc *kproc) ...@@ -200,14 +200,14 @@ static int k3_dsp_rproc_release(struct k3_dsp_rproc *kproc)
ret = kproc->ti_sci->ops.dev_ops.get_device(kproc->ti_sci, ret = kproc->ti_sci->ops.dev_ops.get_device(kproc->ti_sci,
kproc->ti_sci_id); kproc->ti_sci_id);
if (ret) { if (ret) {
dev_err(dev, "module-reset deassert failed, ret = %d\n", ret); dev_err(dev, "module-reset deassert failed (%pe)\n", ERR_PTR(ret));
return ret; return ret;
} }
lreset: lreset:
ret = reset_control_deassert(kproc->reset); ret = reset_control_deassert(kproc->reset);
if (ret) { if (ret) {
dev_err(dev, "local-reset deassert failed, ret = %d\n", ret); dev_err(dev, "local-reset deassert failed, (%pe)\n", ERR_PTR(ret));
if (kproc->ti_sci->ops.dev_ops.put_device(kproc->ti_sci, if (kproc->ti_sci->ops.dev_ops.put_device(kproc->ti_sci,
kproc->ti_sci_id)) kproc->ti_sci_id))
dev_warn(dev, "module-reset assert back failed\n"); dev_warn(dev, "module-reset assert back failed\n");
...@@ -246,7 +246,7 @@ static int k3_dsp_rproc_request_mbox(struct rproc *rproc) ...@@ -246,7 +246,7 @@ static int k3_dsp_rproc_request_mbox(struct rproc *rproc)
*/ */
ret = mbox_send_message(kproc->mbox, (void *)RP_MBOX_ECHO_REQUEST); ret = mbox_send_message(kproc->mbox, (void *)RP_MBOX_ECHO_REQUEST);
if (ret < 0) { if (ret < 0) {
dev_err(dev, "mbox_send_message failed: %d\n", ret); dev_err(dev, "mbox_send_message failed (%pe)\n", ERR_PTR(ret));
mbox_free_channel(kproc->mbox); mbox_free_channel(kproc->mbox);
return ret; return ret;
} }
...@@ -272,8 +272,8 @@ static int k3_dsp_rproc_prepare(struct rproc *rproc) ...@@ -272,8 +272,8 @@ static int k3_dsp_rproc_prepare(struct rproc *rproc)
ret = kproc->ti_sci->ops.dev_ops.get_device(kproc->ti_sci, ret = kproc->ti_sci->ops.dev_ops.get_device(kproc->ti_sci,
kproc->ti_sci_id); kproc->ti_sci_id);
if (ret) if (ret)
dev_err(dev, "module-reset deassert failed, cannot enable internal RAM loading, ret = %d\n", dev_err(dev, "module-reset deassert failed, cannot enable internal RAM loading (%pe)\n",
ret); ERR_PTR(ret));
return ret; return ret;
} }
...@@ -296,7 +296,7 @@ static int k3_dsp_rproc_unprepare(struct rproc *rproc) ...@@ -296,7 +296,7 @@ static int k3_dsp_rproc_unprepare(struct rproc *rproc)
ret = kproc->ti_sci->ops.dev_ops.put_device(kproc->ti_sci, ret = kproc->ti_sci->ops.dev_ops.put_device(kproc->ti_sci,
kproc->ti_sci_id); kproc->ti_sci_id);
if (ret) if (ret)
dev_err(dev, "module-reset assert failed, ret = %d\n", ret); dev_err(dev, "module-reset assert failed (%pe)\n", ERR_PTR(ret));
return ret; return ret;
} }
...@@ -561,9 +561,9 @@ static int k3_dsp_reserved_mem_init(struct k3_dsp_rproc *kproc) ...@@ -561,9 +561,9 @@ static int k3_dsp_reserved_mem_init(struct k3_dsp_rproc *kproc)
num_rmems = of_property_count_elems_of_size(np, "memory-region", num_rmems = of_property_count_elems_of_size(np, "memory-region",
sizeof(phandle)); sizeof(phandle));
if (num_rmems <= 0) { if (num_rmems < 0) {
dev_err(dev, "device does not reserved memory regions, ret = %d\n", dev_err(dev, "device does not reserved memory regions (%pe)\n",
num_rmems); ERR_PTR(num_rmems));
return -EINVAL; return -EINVAL;
} }
if (num_rmems < 2) { if (num_rmems < 2) {
...@@ -575,8 +575,8 @@ static int k3_dsp_reserved_mem_init(struct k3_dsp_rproc *kproc) ...@@ -575,8 +575,8 @@ static int k3_dsp_reserved_mem_init(struct k3_dsp_rproc *kproc)
/* use reserved memory region 0 for vring DMA allocations */ /* use reserved memory region 0 for vring DMA allocations */
ret = of_reserved_mem_device_init_by_idx(dev, np, 0); ret = of_reserved_mem_device_init_by_idx(dev, np, 0);
if (ret) { if (ret) {
dev_err(dev, "device cannot initialize DMA pool, ret = %d\n", dev_err(dev, "device cannot initialize DMA pool (%pe)\n",
ret); ERR_PTR(ret));
return ret; return ret;
} }
...@@ -687,11 +687,8 @@ static int k3_dsp_rproc_probe(struct platform_device *pdev) ...@@ -687,11 +687,8 @@ static int k3_dsp_rproc_probe(struct platform_device *pdev)
return -ENODEV; return -ENODEV;
ret = rproc_of_parse_firmware(dev, 0, &fw_name); ret = rproc_of_parse_firmware(dev, 0, &fw_name);
if (ret) { if (ret)
dev_err(dev, "failed to parse firmware-name property, ret = %d\n", return dev_err_probe(dev, ret, "failed to parse firmware-name property\n");
ret);
return ret;
}
rproc = rproc_alloc(dev, dev_name(dev), &k3_dsp_rproc_ops, fw_name, rproc = rproc_alloc(dev, dev_name(dev), &k3_dsp_rproc_ops, fw_name,
sizeof(*kproc)); sizeof(*kproc));
...@@ -711,39 +708,35 @@ static int k3_dsp_rproc_probe(struct platform_device *pdev) ...@@ -711,39 +708,35 @@ static int k3_dsp_rproc_probe(struct platform_device *pdev)
kproc->ti_sci = ti_sci_get_by_phandle(np, "ti,sci"); kproc->ti_sci = ti_sci_get_by_phandle(np, "ti,sci");
if (IS_ERR(kproc->ti_sci)) { if (IS_ERR(kproc->ti_sci)) {
ret = PTR_ERR(kproc->ti_sci); ret = dev_err_probe(dev, PTR_ERR(kproc->ti_sci),
if (ret != -EPROBE_DEFER) { "failed to get ti-sci handle\n");
dev_err(dev, "failed to get ti-sci handle, ret = %d\n",
ret);
}
kproc->ti_sci = NULL; kproc->ti_sci = NULL;
goto free_rproc; goto free_rproc;
} }
ret = of_property_read_u32(np, "ti,sci-dev-id", &kproc->ti_sci_id); ret = of_property_read_u32(np, "ti,sci-dev-id", &kproc->ti_sci_id);
if (ret) { if (ret) {
dev_err(dev, "missing 'ti,sci-dev-id' property\n"); dev_err_probe(dev, ret, "missing 'ti,sci-dev-id' property\n");
goto put_sci; goto put_sci;
} }
kproc->reset = devm_reset_control_get_exclusive(dev, NULL); kproc->reset = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(kproc->reset)) { if (IS_ERR(kproc->reset)) {
ret = PTR_ERR(kproc->reset); ret = dev_err_probe(dev, PTR_ERR(kproc->reset),
dev_err(dev, "failed to get reset, status = %d\n", ret); "failed to get reset\n");
goto put_sci; goto put_sci;
} }
kproc->tsp = k3_dsp_rproc_of_get_tsp(dev, kproc->ti_sci); kproc->tsp = k3_dsp_rproc_of_get_tsp(dev, kproc->ti_sci);
if (IS_ERR(kproc->tsp)) { if (IS_ERR(kproc->tsp)) {
dev_err(dev, "failed to construct ti-sci proc control, ret = %d\n", ret = dev_err_probe(dev, PTR_ERR(kproc->tsp),
ret); "failed to construct ti-sci proc control\n");
ret = PTR_ERR(kproc->tsp);
goto put_sci; goto put_sci;
} }
ret = ti_sci_proc_request(kproc->tsp); ret = ti_sci_proc_request(kproc->tsp);
if (ret < 0) { if (ret < 0) {
dev_err(dev, "ti_sci_proc_request failed, ret = %d\n", ret); dev_err_probe(dev, ret, "ti_sci_proc_request failed\n");
goto free_tsp; goto free_tsp;
} }
...@@ -753,15 +746,14 @@ static int k3_dsp_rproc_probe(struct platform_device *pdev) ...@@ -753,15 +746,14 @@ static int k3_dsp_rproc_probe(struct platform_device *pdev)
ret = k3_dsp_reserved_mem_init(kproc); ret = k3_dsp_reserved_mem_init(kproc);
if (ret) { if (ret) {
dev_err(dev, "reserved memory init failed, ret = %d\n", ret); dev_err_probe(dev, ret, "reserved memory init failed\n");
goto release_tsp; goto release_tsp;
} }
ret = kproc->ti_sci->ops.dev_ops.is_on(kproc->ti_sci, kproc->ti_sci_id, ret = kproc->ti_sci->ops.dev_ops.is_on(kproc->ti_sci, kproc->ti_sci_id,
NULL, &p_state); NULL, &p_state);
if (ret) { if (ret) {
dev_err(dev, "failed to get initial state, mode cannot be determined, ret = %d\n", dev_err_probe(dev, ret, "failed to get initial state, mode cannot be determined\n");
ret);
goto release_mem; goto release_mem;
} }
...@@ -787,8 +779,7 @@ static int k3_dsp_rproc_probe(struct platform_device *pdev) ...@@ -787,8 +779,7 @@ static int k3_dsp_rproc_probe(struct platform_device *pdev)
if (data->uses_lreset) { if (data->uses_lreset) {
ret = reset_control_status(kproc->reset); ret = reset_control_status(kproc->reset);
if (ret < 0) { if (ret < 0) {
dev_err(dev, "failed to get reset status, status = %d\n", dev_err_probe(dev, ret, "failed to get reset status\n");
ret);
goto release_mem; goto release_mem;
} else if (ret == 0) { } else if (ret == 0) {
dev_warn(dev, "local reset is deasserted for device\n"); dev_warn(dev, "local reset is deasserted for device\n");
...@@ -799,8 +790,7 @@ static int k3_dsp_rproc_probe(struct platform_device *pdev) ...@@ -799,8 +790,7 @@ static int k3_dsp_rproc_probe(struct platform_device *pdev)
ret = rproc_add(rproc); ret = rproc_add(rproc);
if (ret) { if (ret) {
dev_err(dev, "failed to add register device with remoteproc core, status = %d\n", dev_err_probe(dev, ret, "failed to add register device with remoteproc core\n");
ret);
goto release_mem; goto release_mem;
} }
...@@ -813,19 +803,19 @@ static int k3_dsp_rproc_probe(struct platform_device *pdev) ...@@ -813,19 +803,19 @@ static int k3_dsp_rproc_probe(struct platform_device *pdev)
release_tsp: release_tsp:
ret1 = ti_sci_proc_release(kproc->tsp); ret1 = ti_sci_proc_release(kproc->tsp);
if (ret1) if (ret1)
dev_err(dev, "failed to release proc, ret = %d\n", ret1); dev_err(dev, "failed to release proc (%pe)\n", ERR_PTR(ret1));
free_tsp: free_tsp:
kfree(kproc->tsp); kfree(kproc->tsp);
put_sci: put_sci:
ret1 = ti_sci_put_handle(kproc->ti_sci); ret1 = ti_sci_put_handle(kproc->ti_sci);
if (ret1) if (ret1)
dev_err(dev, "failed to put ti_sci handle, ret = %d\n", ret1); dev_err(dev, "failed to put ti_sci handle (%pe)\n", ERR_PTR(ret1));
free_rproc: free_rproc:
rproc_free(rproc); rproc_free(rproc);
return ret; return ret;
} }
static int k3_dsp_rproc_remove(struct platform_device *pdev) static void k3_dsp_rproc_remove(struct platform_device *pdev)
{ {
struct k3_dsp_rproc *kproc = platform_get_drvdata(pdev); struct k3_dsp_rproc *kproc = platform_get_drvdata(pdev);
struct rproc *rproc = kproc->rproc; struct rproc *rproc = kproc->rproc;
...@@ -835,8 +825,9 @@ static int k3_dsp_rproc_remove(struct platform_device *pdev) ...@@ -835,8 +825,9 @@ static int k3_dsp_rproc_remove(struct platform_device *pdev)
if (rproc->state == RPROC_ATTACHED) { if (rproc->state == RPROC_ATTACHED) {
ret = rproc_detach(rproc); ret = rproc_detach(rproc);
if (ret) { if (ret) {
dev_err(dev, "failed to detach proc, ret = %d\n", ret); /* Note this error path leaks resources */
return ret; dev_err(dev, "failed to detach proc (%pe)\n", ERR_PTR(ret));
return;
} }
} }
...@@ -844,18 +835,16 @@ static int k3_dsp_rproc_remove(struct platform_device *pdev) ...@@ -844,18 +835,16 @@ static int k3_dsp_rproc_remove(struct platform_device *pdev)
ret = ti_sci_proc_release(kproc->tsp); ret = ti_sci_proc_release(kproc->tsp);
if (ret) if (ret)
dev_err(dev, "failed to release proc, ret = %d\n", ret); dev_err(dev, "failed to release proc (%pe)\n", ERR_PTR(ret));
kfree(kproc->tsp); kfree(kproc->tsp);
ret = ti_sci_put_handle(kproc->ti_sci); ret = ti_sci_put_handle(kproc->ti_sci);
if (ret) if (ret)
dev_err(dev, "failed to put ti_sci handle, ret = %d\n", ret); dev_err(dev, "failed to put ti_sci handle (%pe)\n", ERR_PTR(ret));
k3_dsp_reserved_mem_exit(kproc); k3_dsp_reserved_mem_exit(kproc);
rproc_free(kproc->rproc); rproc_free(kproc->rproc);
return 0;
} }
static const struct k3_dsp_mem_data c66_mems[] = { static const struct k3_dsp_mem_data c66_mems[] = {
...@@ -906,7 +895,7 @@ MODULE_DEVICE_TABLE(of, k3_dsp_of_match); ...@@ -906,7 +895,7 @@ MODULE_DEVICE_TABLE(of, k3_dsp_of_match);
static struct platform_driver k3_dsp_rproc_driver = { static struct platform_driver k3_dsp_rproc_driver = {
.probe = k3_dsp_rproc_probe, .probe = k3_dsp_rproc_probe,
.remove = k3_dsp_rproc_remove, .remove_new = k3_dsp_rproc_remove,
.driver = { .driver = {
.name = "k3-dsp-rproc", .name = "k3-dsp-rproc",
.of_match_table = k3_dsp_of_match, .of_match_table = k3_dsp_of_match,
......
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