[PATCH 3/8] media: uniphier: add submodules of HSC MPEG2-TS I/O driver
From: Katsuhiro Suzuki
Date: Wed May 30 2018 - 05:11:13 EST
This patch adds submodules of HSC for UniPhier SoCs.
These work as follows:
ucode: Load uCode and start subsystems
css : Switch stream path
ts : Receive MPEG2-TS clock and signal
dma : Transfer MPEG2-TS data bytes to main memory
Signed-off-by: Katsuhiro Suzuki <suzuki.katsuhiro@xxxxxxxxxxxxx>
---
drivers/media/platform/uniphier/Makefile | 3 +
drivers/media/platform/uniphier/hsc-css.c | 258 ++++++++++++
drivers/media/platform/uniphier/hsc-dma.c | 302 ++++++++++++++
drivers/media/platform/uniphier/hsc-ts.c | 99 +++++
drivers/media/platform/uniphier/hsc-ucode.c | 436 ++++++++++++++++++++
5 files changed, 1098 insertions(+)
create mode 100644 drivers/media/platform/uniphier/hsc-css.c
create mode 100644 drivers/media/platform/uniphier/hsc-dma.c
create mode 100644 drivers/media/platform/uniphier/hsc-ts.c
create mode 100644 drivers/media/platform/uniphier/hsc-ucode.c
diff --git a/drivers/media/platform/uniphier/Makefile b/drivers/media/platform/uniphier/Makefile
index f66554cd5c45..92536bc56b31 100644
--- a/drivers/media/platform/uniphier/Makefile
+++ b/drivers/media/platform/uniphier/Makefile
@@ -1 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
+uniphier-dvb-y += hsc-ucode.o hsc-css.o hsc-ts.o hsc-dma.o
+
+obj-$(CONFIG_DVB_UNIPHIER) += uniphier-dvb.o
diff --git a/drivers/media/platform/uniphier/hsc-css.c b/drivers/media/platform/uniphier/hsc-css.c
new file mode 100644
index 000000000000..baa0a15ca98e
--- /dev/null
+++ b/drivers/media/platform/uniphier/hsc-css.c
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Socionext UniPhier DVB driver for High-speed Stream Controller (HSC).
+// CSS (Cross Stream Switch) connects MPEG2-TS input port and output port.
+//
+// Copyright (c) 2018 Socionext Inc.
+
+#include <linux/bitfield.h>
+#include <linux/kernel.h>
+
+#include "hsc.h"
+#include "hsc-reg.h"
+
+enum HSC_TS_IN hsc_css_out_to_ts_in(enum HSC_CSS_OUT out)
+{
+ if (out >= HSC_CSS_OUT_TSI0 && out <= HSC_CSS_OUT_TSI9)
+ return HSC_TSI0 + (out - HSC_CSS_OUT_TSI0);
+
+ return -1;
+}
+
+enum HSC_DPLL_SRC hsc_css_out_to_dpll_src(enum HSC_CSS_OUT out)
+{
+ if (out >= HSC_CSS_OUT_TSI0 && out <= HSC_CSS_OUT_TSI9)
+ return HSC_DPLL_SRC_TSI0 + (out - HSC_CSS_OUT_TSI0);
+
+ return -1;
+}
+
+static bool css_in_is_valid(struct hsc_chip *chip, enum HSC_CSS_IN in)
+{
+ if (in >= chip->spec->num_css_in)
+ return false;
+
+ return true;
+}
+
+static bool css_out_is_valid(struct hsc_chip *chip, enum HSC_CSS_OUT out)
+{
+ if (out >= chip->spec->num_css_out)
+ return false;
+
+ return true;
+}
+
+static const struct hsc_spec_css_in *css_in_get_spec(struct hsc_chip *chip,
+ enum HSC_CSS_IN in)
+{
+ const struct hsc_spec_css_in *spec = chip->spec->css_in;
+
+ if (!css_in_is_valid(chip, in))
+ return NULL;
+
+ return &spec[in];
+}
+
+static const struct hsc_spec_css_out *css_out_get_spec(struct hsc_chip *chip,
+ enum HSC_CSS_OUT out)
+{
+ const struct hsc_spec_css_out *spec = chip->spec->css_out;
+
+ if (!css_out_is_valid(chip, out))
+ return NULL;
+
+ return &spec[out];
+}
+
+int hsc_dpll_get_src(struct hsc_chip *chip, enum HSC_DPLL dpll,
+ enum HSC_DPLL_SRC *src)
+{
+ struct regmap *r = chip->regmap;
+ u32 v;
+
+ if (!src || dpll >= HSC_DPLL_NUM)
+ return -EINVAL;
+
+ regmap_read(r, CSS_DPCTRL(dpll), &v);
+ *src = ffs(v & CSS_DPCTRL_DPSEL_MASK) - 1;
+
+ return 0;
+}
+
+/**
+ * Select source clock of DPLL.
+ *
+ * @dpll: ID of DPLL
+ * @src : ID of clock source or HSC_DPLL_SRC_NONE to disconnect
+ */
+int hsc_dpll_set_src(struct hsc_chip *chip, enum HSC_DPLL dpll,
+ enum HSC_DPLL_SRC src)
+{
+ struct regmap *r = chip->regmap;
+ u32 v = 0;
+
+ if (dpll >= HSC_DPLL_NUM || src >= HSC_DPLL_SRC_NUM)
+ return -EINVAL;
+
+ if (src != HSC_DPLL_SRC_NONE)
+ v = 1 << src;
+
+ regmap_write(r, CSS_DPCTRL(dpll), v);
+
+ return 0;
+}
+
+static int hsc_css_get_polarity(struct hsc_chip *chip,
+ const struct hsc_css_pol *pol,
+ bool *sync_bit, bool *val_bit, bool *clk_fall)
+{
+ struct regmap *r = chip->regmap;
+ u32 v;
+
+ if (!sync_bit || !val_bit || !clk_fall || !pol->valid)
+ return -EINVAL;
+
+ regmap_read(r, pol->reg, &v);
+
+ *sync_bit = !!(v & BIT(pol->sft_sync));
+ *val_bit = !!(v & BIT(pol->sft_val));
+ *clk_fall = !!(v & BIT(pol->sft_clk));
+
+ return 0;
+}
+
+/**
+ * Setup signal polarity of TS signals.
+ *
+ * @sync_bit : true : The sync signal keeps only 1bit period.
+ * false: The sync signal keeps during 8bits period.
+ * @valid_bit: true : The valid signal does not keep during 8bits period.
+ * false: The valid signal keeps during 8bits period.
+ * @clk_fall : true : Latch the data at falling edge of clock signal.
+ * false: Latch the data at rising edge of clock signal.
+ */
+static int hsc_css_set_polarity(struct hsc_chip *chip,
+ const struct hsc_css_pol *pol,
+ bool sync_bit, bool val_bit, bool clk_fall)
+{
+ struct regmap *r = chip->regmap;
+ u32 m = 0, v = 0;
+
+ if (!pol->valid)
+ return -EINVAL;
+
+ if (pol->sft_sync != -1) {
+ m |= BIT(pol->sft_sync);
+ if (sync_bit)
+ v |= BIT(pol->sft_sync);
+ }
+
+ if (pol->sft_val != -1) {
+ m |= BIT(pol->sft_val);
+ if (val_bit)
+ v |= BIT(pol->sft_val);
+ }
+
+ if (pol->sft_clk != -1) {
+ m |= BIT(pol->sft_clk);
+ if (clk_fall)
+ v |= BIT(pol->sft_clk);
+ }
+
+ regmap_update_bits(r, pol->reg, m, v);
+
+ return 0;
+}
+
+int hsc_css_in_get_polarity(struct hsc_chip *chip, enum HSC_CSS_IN in,
+ bool *sync_bit, bool *val_bit, bool *clk_fall)
+{
+ const struct hsc_spec_css_in *speci = css_in_get_spec(chip, in);
+
+ if (!speci)
+ return -EINVAL;
+
+ return hsc_css_get_polarity(chip, &speci->pol,
+ sync_bit, val_bit, clk_fall);
+}
+
+int hsc_css_in_set_polarity(struct hsc_chip *chip, enum HSC_CSS_IN in,
+ bool sync_bit, bool val_bit, bool clk_fall)
+{
+ const struct hsc_spec_css_in *speci = css_in_get_spec(chip, in);
+
+ if (!speci)
+ return -EINVAL;
+
+ return hsc_css_set_polarity(chip, &speci->pol,
+ sync_bit, val_bit, clk_fall);
+}
+
+int hsc_css_out_get_polarity(struct hsc_chip *chip, enum HSC_CSS_OUT out,
+ bool *sync_bit, bool *val_bit, bool *clk_fall)
+{
+ const struct hsc_spec_css_out *speco = css_out_get_spec(chip, out);
+
+ if (!speco)
+ return -EINVAL;
+
+ return hsc_css_get_polarity(chip, &speco->pol,
+ sync_bit, val_bit, clk_fall);
+}
+
+int hsc_css_out_set_polarity(struct hsc_chip *chip, enum HSC_CSS_OUT out,
+ bool sync_bit, bool val_bit, bool clk_fall)
+{
+ const struct hsc_spec_css_out *speco = css_out_get_spec(chip, out);
+
+ if (!speco)
+ return -EINVAL;
+
+ return hsc_css_set_polarity(chip, &speco->pol,
+ sync_bit, val_bit, clk_fall);
+}
+
+int hsc_css_out_get_src(struct hsc_chip *chip, enum HSC_CSS_IN *in,
+ enum HSC_CSS_OUT out, bool *en)
+{
+ struct regmap *r = chip->regmap;
+ const struct hsc_spec_css_out *speco = css_out_get_spec(chip, out);
+ u32 v;
+
+ if (!in || !en || !speco || !speco->sel.valid)
+ return -EINVAL;
+
+ regmap_read(r, speco->sel.reg, &v);
+ *in = field_get(speco->sel.mask, v);
+
+ regmap_read(r, CSS_OUTPUTENABLE, &v);
+ *en = !!(v & BIT(out));
+
+ return 0;
+}
+
+/**
+ * Connect the input port and output port using CSS (Cross Stream Switch).
+ *
+ * @in : Input port number.
+ * @out : Output port number.
+ * @en : false: Disable this path.
+ * true : Enable this path.
+ */
+int hsc_css_out_set_src(struct hsc_chip *chip, enum HSC_CSS_IN in,
+ enum HSC_CSS_OUT out, bool en)
+{
+ struct regmap *r = chip->regmap;
+ const struct hsc_spec_css_out *speco = css_out_get_spec(chip, out);
+
+ if (!css_in_is_valid(chip, in) || !speco || !speco->sel.valid)
+ return -EINVAL;
+
+ regmap_update_bits(r, speco->sel.reg, speco->sel.mask,
+ field_prep(speco->sel.mask, in));
+
+ regmap_update_bits(r, CSS_OUTPUTENABLE, BIT(out), (en) ? ~0 : 0);
+
+ return 0;
+}
diff --git a/drivers/media/platform/uniphier/hsc-dma.c b/drivers/media/platform/uniphier/hsc-dma.c
new file mode 100644
index 000000000000..0b3e471a68f7
--- /dev/null
+++ b/drivers/media/platform/uniphier/hsc-dma.c
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Socionext UniPhier DVB driver for High-speed Stream Controller (HSC).
+// MPEG2-TS DMA control.
+//
+// Copyright (c) 2018 Socionext Inc.
+
+#include <linux/bitfield.h>
+#include <linux/kernel.h>
+
+#include "hsc.h"
+#include "hsc-reg.h"
+
+u64 hsc_rb_cnt(struct hsc_dma_buf *buf)
+{
+ if (buf->rd_offs <= buf->wr_offs)
+ return buf->wr_offs - buf->rd_offs;
+ else
+ return buf->size - (buf->rd_offs - buf->wr_offs);
+}
+
+u64 hsc_rb_cnt_to_end(struct hsc_dma_buf *buf)
+{
+ if (buf->rd_offs <= buf->wr_offs)
+ return buf->wr_offs - buf->rd_offs;
+ else
+ return buf->size - buf->rd_offs;
+}
+
+u64 hsc_rb_space(struct hsc_dma_buf *buf)
+{
+ if (buf->rd_offs <= buf->wr_offs)
+ return buf->size - (buf->wr_offs - buf->rd_offs) - 8;
+ else
+ return buf->rd_offs - buf->wr_offs - 8;
+}
+
+u64 hsc_rb_space_to_end(struct hsc_dma_buf *buf)
+{
+ if (buf->rd_offs > buf->wr_offs)
+ return buf->rd_offs - buf->wr_offs - 8;
+ else if (buf->rd_offs > 0)
+ return buf->size - buf->wr_offs;
+ else
+ return buf->size - buf->wr_offs - 8;
+}
+
+static void dma_set_buffer(struct hsc_chip *chip, int dma_ch,
+ u64 bg, u64 ed)
+{
+ struct regmap *r = chip->regmap;
+
+ regmap_write(r, CDMBC_RBBGNADRSD(dma_ch), bg);
+ regmap_write(r, CDMBC_RBBGNADRSU(dma_ch), bg >> 32);
+ regmap_write(r, CDMBC_RBENDADRSD(dma_ch), ed);
+ regmap_write(r, CDMBC_RBENDADRSU(dma_ch), ed >> 32);
+}
+
+static u64 dma_get_rp(struct hsc_chip *chip, int dma_ch)
+{
+ struct regmap *r = chip->regmap;
+ u32 d, u;
+
+ regmap_read(r, CDMBC_RBRDPTRD(dma_ch), &d);
+ regmap_read(r, CDMBC_RBRDPTRU(dma_ch), &u);
+
+ return ((u64)u << 32) | d;
+}
+
+static void dma_set_rp(struct hsc_chip *chip, int dma_ch, u64 pos)
+{
+ struct regmap *r = chip->regmap;
+
+ regmap_write(r, CDMBC_RBRDPTRD(dma_ch), pos);
+ regmap_write(r, CDMBC_RBRDPTRU(dma_ch), pos >> 32);
+}
+
+static u64 dma_get_wp(struct hsc_chip *chip, int dma_ch)
+{
+ struct regmap *r = chip->regmap;
+ u32 d, u;
+
+ regmap_read(r, CDMBC_RBWRPTRD(dma_ch), &d);
+ regmap_read(r, CDMBC_RBWRPTRU(dma_ch), &u);
+
+ return ((u64)u << 32) | d;
+}
+
+static void dma_set_wp(struct hsc_chip *chip, int dma_ch, u64 pos)
+{
+ struct regmap *r = chip->regmap;
+
+ regmap_write(r, CDMBC_RBWRPTRD(dma_ch), pos);
+ regmap_write(r, CDMBC_RBWRPTRU(dma_ch), pos >> 32);
+}
+
+static void dma_set_chkp(struct hsc_chip *chip, int dma_ch, u64 pos)
+{
+ struct regmap *r = chip->regmap;
+
+ regmap_write(r, CDMBC_CHIRADRSD(dma_ch), pos);
+ regmap_write(r, CDMBC_CHIRADRSU(dma_ch), pos >> 32);
+}
+
+static void dma_set_enable(struct hsc_chip *chip, int dma_ch,
+ const struct hsc_dma_en *dma_en, bool en)
+{
+ struct regmap *r = chip->regmap;
+ u32 v;
+ bool now;
+
+ regmap_read(r, dma_en->reg, &v);
+ now = !!(v & BIT(dma_en->sft_toggle));
+
+ /* Toggle DMA state if needed */
+ if ((en && !now) || (!en && now))
+ regmap_write(r, dma_en->reg, BIT(dma_en->sft_toggle));
+}
+
+static bool dma_in_is_valid(struct hsc_chip *chip, enum HSC_DMA_IN in)
+{
+ if (in >= chip->spec->num_dma_in ||
+ !chip->spec->dma_in[in].intr.valid)
+ return false;
+
+ return true;
+}
+
+int hsc_dma_in_init(struct hsc_dma_in *dma_in, struct hsc_chip *chip,
+ enum HSC_DMA_IN in, struct hsc_dma_buf *buf)
+{
+ if (!dma_in || !dma_in_is_valid(chip, in))
+ return -EINVAL;
+
+ dma_in->chip = chip;
+ dma_in->id = in;
+ dma_in->spec = &chip->spec->dma_in[in];
+ dma_in->buf = buf;
+
+ return 0;
+}
+
+void hsc_dma_in_start(struct hsc_dma_in *dma_in, bool en)
+{
+ struct hsc_chip *chip = dma_in->chip;
+ const struct hsc_spec_dma *spec = dma_in->spec;
+ struct hsc_dma_buf *buf = dma_in->buf;
+ struct regmap *r = chip->regmap;
+ u64 bg, ed;
+ u32 v;
+
+ bg = buf->phys;
+ ed = buf->phys + buf->size;
+ dma_set_buffer(chip, spec->dma_ch, bg, ed);
+
+ buf->rd_offs = 0;
+ buf->wr_offs = 0;
+ buf->chk_offs = buf->size_chk;
+ dma_set_rp(chip, spec->dma_ch, buf->rd_offs + buf->phys);
+ dma_set_wp(chip, spec->dma_ch, buf->wr_offs + buf->phys);
+ dma_set_chkp(chip, spec->dma_ch, buf->chk_offs + buf->phys);
+
+ regmap_update_bits(r, CDMBC_CHSRCAMODE(spec->dma_ch),
+ CDMBC_CHAMODE_TYPE_RB, ~0);
+ regmap_update_bits(r, CDMBC_CHCTRL1(spec->dma_ch),
+ CDMBC_CHCTRL1_IND_SIZE_UND, ~0);
+
+ v = (en) ? ~0 : 0;
+ regmap_update_bits(r, CDMBC_CHIE(spec->dma_ch), CDMBC_CHI_TRANSIT, v);
+ regmap_update_bits(r, spec->intr.reg, BIT(spec->intr.sft_intr), v);
+
+ dma_set_enable(chip, spec->dma_ch, &spec->en, en);
+}
+
+void hsc_dma_in_sync(struct hsc_dma_in *dma_in)
+{
+ struct hsc_chip *chip = dma_in->chip;
+ const struct hsc_spec_dma *spec = dma_in->spec;
+ struct hsc_dma_buf *buf = dma_in->buf;
+
+ buf->rd_offs = dma_get_rp(chip, spec->dma_ch) - buf->phys;
+ dma_set_wp(chip, spec->dma_ch, buf->rd_offs + buf->phys);
+ dma_set_chkp(chip, spec->dma_ch, buf->chk_offs + buf->phys);
+}
+
+int hsc_dma_in_get_intr(struct hsc_dma_in *dma_in, u32 *stat)
+{
+ struct regmap *r = dma_in->chip->regmap;
+
+ if (!stat)
+ return -EINVAL;
+
+ regmap_read(r, CDMBC_CHID(dma_in->spec->dma_ch), stat);
+
+ return 0;
+}
+
+void hsc_dma_in_clear_intr(struct hsc_dma_in *dma_in, u32 v)
+{
+ struct regmap *r = dma_in->chip->regmap;
+
+ regmap_write(r, CDMBC_CHIR(dma_in->spec->dma_ch), v);
+}
+
+static bool dma_out_is_valid(struct hsc_chip *chip, enum HSC_DMA_OUT out)
+{
+ if (out >= chip->spec->num_dma_out ||
+ !chip->spec->dma_out[out].intr.valid)
+ return false;
+
+ return true;
+}
+
+int hsc_dma_out_init(struct hsc_dma_out *dma_out, struct hsc_chip *chip,
+ enum HSC_DMA_OUT out, struct hsc_dma_buf *buf)
+{
+ if (!dma_out || !dma_out_is_valid(chip, out))
+ return -EINVAL;
+
+ dma_out->chip = chip;
+ dma_out->id = out;
+ dma_out->spec = &chip->spec->dma_out[out];
+ dma_out->buf = buf;
+
+ return 0;
+}
+
+void hsc_dma_out_set_src_ts_in(struct hsc_dma_out *dma_out,
+ enum HSC_TS_IN ts_in)
+{
+ struct regmap *r = dma_out->chip->regmap;
+ const struct hsc_spec_dma *spec = dma_out->spec;
+ u32 m, v;
+
+ m = CDMBC_CHTDCTRLH_STREM_MASK |
+ CDMBC_CHTDCTRLH_ALL_EN;
+ v = FIELD_PREP(CDMBC_CHTDCTRLH_STREM_MASK, ts_in) |
+ CDMBC_CHTDCTRLH_ALL_EN;
+ regmap_update_bits(r, CDMBC_CHTDCTRLH(spec->dma_ch), m, v);
+}
+
+void hsc_dma_out_start(struct hsc_dma_out *dma_out, bool en)
+{
+ struct hsc_chip *chip = dma_out->chip;
+ const struct hsc_spec_dma *spec = dma_out->spec;
+ struct hsc_dma_buf *buf = dma_out->buf;
+ struct regmap *r = chip->regmap;
+ u64 bg, ed;
+ u32 v;
+
+ bg = buf->phys;
+ ed = buf->phys + buf->size;
+ dma_set_buffer(chip, spec->dma_ch, bg, ed);
+
+ buf->rd_offs = 0;
+ buf->wr_offs = 0;
+ buf->chk_offs = buf->size_chk;
+ dma_set_rp(chip, spec->dma_ch, buf->rd_offs + buf->phys);
+ dma_set_wp(chip, spec->dma_ch, buf->wr_offs + buf->phys);
+ dma_set_chkp(chip, spec->dma_ch, buf->chk_offs + buf->phys);
+
+ regmap_update_bits(r, CDMBC_CHDSTAMODE(spec->dma_ch),
+ CDMBC_CHAMODE_TYPE_RB, ~0);
+ regmap_update_bits(r, CDMBC_CHCTRL1(spec->dma_ch),
+ CDMBC_CHCTRL1_IND_SIZE_UND, ~0);
+
+ v = (en) ? ~0 : 0;
+ regmap_update_bits(r, CDMBC_CHIE(spec->dma_ch), CDMBC_CHI_TRANSIT, v);
+ regmap_update_bits(r, spec->intr.reg, BIT(spec->intr.sft_intr), v);
+
+ dma_set_enable(chip, spec->dma_ch, &spec->en, en);
+}
+
+void hsc_dma_out_sync(struct hsc_dma_out *dma_out)
+{
+ struct hsc_chip *chip = dma_out->chip;
+ const struct hsc_spec_dma *spec = dma_out->spec;
+ struct hsc_dma_buf *buf = dma_out->buf;
+
+ dma_set_rp(chip, spec->dma_ch, buf->rd_offs + buf->phys);
+ buf->wr_offs = dma_get_wp(chip, spec->dma_ch) - buf->phys;
+ dma_set_chkp(chip, spec->dma_ch, buf->chk_offs + buf->phys);
+}
+
+int hsc_dma_out_get_intr(struct hsc_dma_out *dma_out, u32 *stat)
+{
+ struct regmap *r = dma_out->chip->regmap;
+
+ if (!stat)
+ return -EINVAL;
+
+ regmap_read(r, CDMBC_CHID(dma_out->spec->dma_ch), stat);
+
+ return 0;
+}
+
+void hsc_dma_out_clear_intr(struct hsc_dma_out *dma_out, u32 clear)
+{
+ struct regmap *r = dma_out->chip->regmap;
+
+ regmap_write(r, CDMBC_CHIR(dma_out->spec->dma_ch), clear);
+}
diff --git a/drivers/media/platform/uniphier/hsc-ts.c b/drivers/media/platform/uniphier/hsc-ts.c
new file mode 100644
index 000000000000..4539c3280021
--- /dev/null
+++ b/drivers/media/platform/uniphier/hsc-ts.c
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Socionext UniPhier DVB driver for High-speed Stream Controller (HSC).
+// MPEG2-TS input/output port setting.
+//
+// Copyright (c) 2018 Socionext Inc.
+
+#include <linux/bitfield.h>
+#include <linux/kernel.h>
+
+#include "hsc.h"
+#include "hsc-reg.h"
+
+#define PARAMA_OFFSET_TS 0x02
+#define PARAMA_LOOPADDR_TS 0x31
+#define PARAMA_COUNT_TS 0xc4
+
+static bool ts_in_is_valid(struct hsc_chip *chip, enum HSC_TS_IN in)
+{
+ if (in >= chip->spec->num_ts_in || !chip->spec->ts_in[in].intr.valid)
+ return false;
+
+ return true;
+}
+
+static const struct hsc_spec_ts *ts_in_get_spec(struct hsc_chip *chip,
+ enum HSC_TS_IN in)
+{
+ const struct hsc_spec_ts *spec = chip->spec->ts_in;
+
+ if (!ts_in_is_valid(chip, in))
+ return NULL;
+
+ return &spec[in];
+}
+
+int hsc_ts_in_set_enable(struct hsc_chip *chip, enum HSC_TS_IN in, bool en)
+{
+ struct regmap *r = chip->regmap;
+ const struct hsc_spec_ts *speci = ts_in_get_spec(chip, in);
+ u32 m, v;
+
+ if (!speci)
+ return -EINVAL;
+
+ m = TSI_SYNCCNTROL_FRAME_MASK;
+ v = TSI_SYNCCNTROL_FRAME_EXTSYNC2;
+ regmap_update_bits(r, TSI_SYNCCNTROL(in), m, v);
+
+ m = TSI_CONFIG_ATSMD_MASK | TSI_CONFIG_STCMD_MASK |
+ TSI_CONFIG_CHEN_START;
+ v = TSI_CONFIG_ATSMD_DPLL | TSI_CONFIG_STCMD_DPLL;
+ if (en)
+ v |= TSI_CONFIG_CHEN_START;
+ regmap_update_bits(r, TSI_CONFIG(in), m, v);
+
+ v = (en) ? ~0 : 0;
+ regmap_update_bits(r, TSI_INTREN(in),
+ TSI_INTR_SERR | TSI_INTR_LOST, v);
+ regmap_update_bits(r, speci->intr.reg, BIT(speci->intr.sft_intr), v);
+
+ return 0;
+}
+
+int hsc_ts_in_set_dmaparam(struct hsc_chip *chip, enum HSC_TS_IN in,
+ enum HSC_TSIF_FMT ifmt)
+{
+ struct regmap *r = chip->regmap;
+ u32 v, ats, offset, loop, cnt;
+
+ if (!ts_in_is_valid(chip, in))
+ return -EINVAL;
+
+ switch (ifmt) {
+ case HSC_TSIF_MPEG2_TS:
+ ats = 0;
+ offset = PARAMA_OFFSET_TS;
+ loop = PARAMA_LOOPADDR_TS;
+ cnt = PARAMA_COUNT_TS;
+ break;
+ case HSC_TSIF_MPEG2_TS_ATS:
+ ats = TSI_CONFIG_ATSADD_ON;
+ offset = PARAMA_OFFSET_TS;
+ loop = PARAMA_LOOPADDR_TS;
+ cnt = PARAMA_COUNT_TS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_update_bits(r, TSI_CONFIG(in), TSI_CONFIG_ATSADD_ON, ats);
+
+ v = FIELD_PREP(SBC_DMAPARAMA_OFFSET_MASK, offset) |
+ FIELD_PREP(SBC_DMAPARAMA_LOOPADDR_MASK, loop) |
+ FIELD_PREP(SBC_DMAPARAMA_COUNT_MASK, cnt);
+ regmap_write(r, SBC_DMAPARAMA(in), v);
+
+ return 0;
+}
diff --git a/drivers/media/platform/uniphier/hsc-ucode.c b/drivers/media/platform/uniphier/hsc-ucode.c
new file mode 100644
index 000000000000..bad1d70b0968
--- /dev/null
+++ b/drivers/media/platform/uniphier/hsc-ucode.c
@@ -0,0 +1,436 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Socionext UniPhier DVB driver for High-speed Stream Controller (HSC).
+// Core init and uCode loader.
+//
+// Copyright (c) 2018 Socionext Inc.
+
+#include <linux/bitfield.h>
+#include <linux/firmware.h>
+#include <linux/kernel.h>
+
+#include "hsc.h"
+#include "hsc-reg.h"
+
+struct hsc_cip_file_dma_param {
+ u32 cip_file_ch;
+ dma_addr_t cip_r_sdram;
+ dma_addr_t cip_w_sdram;
+ size_t inter_size;
+ size_t total_size;
+ u8 key_id1;
+ u8 key_id0;
+ u8 endian;
+ int id1_en;
+ int push;
+};
+
+static void core_start(struct hsc_chip *chip)
+{
+ struct regmap *r = chip->regmap;
+
+ regmap_write(r, IOB_RESET0, ~0);
+ regmap_write(r, IOB_RESET1, ~0);
+
+ regmap_write(r, IOB_CLKSTOP, 0);
+ /* Deassert all internal resets, but AP core is later for uCode */
+ regmap_write(r, IOB_RESET0, IOB_RESET0_APCORE);
+ regmap_write(r, IOB_RESET1, 0);
+
+ /* Halt SPU for uCode */
+ regmap_write(r, IOB_DEBUG, IOB_DEBUG_SPUHALT);
+}
+
+static void core_stop(struct hsc_chip *chip)
+{
+ struct regmap *r = chip->regmap;
+
+ regmap_write(r, IOB_RESET0, 0);
+ regmap_write(r, IOB_RESET1, 0);
+
+ regmap_write(r, IOB_CLKSTOP, ~0);
+}
+
+static void core_clear_ram(struct hsc_chip *chip)
+{
+ struct regmap *r = chip->regmap;
+ const struct hsc_spec_init_ram *rams = chip->spec->init_rams;
+ size_t i, s;
+
+ for (i = 0; i < chip->spec->num_init_rams; i++)
+ for (s = 0; s < rams[i].size; s += 4)
+ regmap_write(r, rams[i].addr + s, rams[i].pattern);
+}
+
+static void core_start_spu(struct hsc_chip *chip)
+{
+ struct regmap *r = chip->regmap;
+
+ regmap_write(r, IOB_DEBUG, 0);
+}
+
+static void core_start_ap(struct hsc_chip *chip)
+{
+ struct regmap *r = chip->regmap;
+
+ regmap_write(r, IOB_RESET0, 0);
+}
+
+static int ucode_set_data_addr(struct hsc_chip *chip, int mode)
+{
+ struct regmap *r = chip->regmap;
+ dma_addr_t addr;
+
+ switch (mode) {
+ case HSC_UCODE_SPU_0:
+ case HSC_UCODE_SPU_1:
+ addr = chip->ucode_spu.phys_data;
+ regmap_write(r, UCODE_DLADDR0, addr);
+ regmap_write(r, UCODE_DLADDR1, addr >> 32);
+ break;
+ case HSC_UCODE_ACE:
+ addr = chip->ucode_am.phys_data;
+ regmap_write(r, CIP_UCODEADDR_AM0, addr);
+ regmap_write(r, CIP_UCODEADDR_AM1, addr >> 32);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void file_channel_dma_set(struct hsc_chip *chip,
+ struct hsc_cip_file_dma_param *p)
+{
+ struct regmap *r = chip->regmap;
+ u32 cipr_rsc_no;
+ u32 cipw_rsc_no;
+ dma_addr_t cipr_sdram_start;
+ dma_addr_t cipw_sdram_start;
+ dma_addr_t cipr_sdram_end;
+ dma_addr_t cipw_sdram_end;
+ u32 v;
+
+ cipr_rsc_no = HSC_CIP_FILE_TO_CIPR_DMCH(p->cip_file_ch);
+ cipw_rsc_no = HSC_CIP_FILE_TO_CIPW_DMCH(p->cip_file_ch);
+ cipr_sdram_start = p->cip_r_sdram;
+ cipw_sdram_start = p->cip_w_sdram;
+ cipr_sdram_end = p->cip_r_sdram + p->total_size;
+ cipw_sdram_end = p->cip_w_sdram + p->total_size;
+
+ /* For CIP Read */
+ v = FIELD_PREP(CDMBC_CHCTRL1_LINKCH1_MASK, 1) |
+ FIELD_PREP(CDMBC_CHCTRL1_STATSEL_MASK, 4) |
+ CDMBC_CHCTRL1_TYPE_INTERMIT;
+ regmap_write(r, CDMBC_CHCTRL1(cipr_rsc_no), v);
+
+ regmap_write(r, CDMBC_CHCAUSECTRL(cipr_rsc_no), 0);
+
+ v = FIELD_PREP(CDMBC_CHAMODE_ENDIAN_MASK, p->endian) |
+ FIELD_PREP(CDMBC_CHAMODE_AUPDT_MASK, 0) |
+ CDMBC_CHAMODE_TYPE_RB;
+ regmap_write(r, CDMBC_CHSRCAMODE(cipr_rsc_no), v);
+
+ v = FIELD_PREP(CDMBC_CHAMODE_ENDIAN_MASK, 1) |
+ FIELD_PREP(CDMBC_CHAMODE_AUPDT_MASK, 2);
+ regmap_write(r, CDMBC_CHDSTAMODE(cipr_rsc_no), v);
+
+ v = FIELD_PREP(CDMBC_CHDSTSTRTADRS_TID_MASK, 0xc) |
+ FIELD_PREP(CDMBC_CHDSTSTRTADRS_ID1_EN_MASK, p->id1_en) |
+ FIELD_PREP(CDMBC_CHDSTSTRTADRS_KEY_ID1_MASK, p->key_id1) |
+ FIELD_PREP(CDMBC_CHDSTSTRTADRS_KEY_ID0_MASK, p->key_id0);
+ regmap_write(r, CDMBC_CHDSTSTRTADRSD(cipr_rsc_no), v);
+
+ regmap_write(r, CDMBC_CHSIZE(cipr_rsc_no), p->inter_size);
+
+ /* For CIP Write */
+ v = FIELD_PREP(CDMBC_CHCTRL1_LINKCH1_MASK, 5) |
+ FIELD_PREP(CDMBC_CHCTRL1_STATSEL_MASK, 4) |
+ CDMBC_CHCTRL1_TYPE_INTERMIT |
+ CDMBC_CHCTRL1_IND_SIZE_UND;
+ regmap_write(r, CDMBC_CHCTRL1(cipw_rsc_no), v);
+
+ v = FIELD_PREP(CDMBC_CHAMODE_ENDIAN_MASK, 1) |
+ FIELD_PREP(CDMBC_CHAMODE_AUPDT_MASK, 2);
+ regmap_write(r, CDMBC_CHSRCAMODE(cipw_rsc_no), v);
+
+ v = FIELD_PREP(CDMBC_CHAMODE_ENDIAN_MASK, p->endian) |
+ FIELD_PREP(CDMBC_CHAMODE_AUPDT_MASK, 0) |
+ CDMBC_CHAMODE_TYPE_RB;
+ regmap_write(r, CDMBC_CHDSTAMODE(cipw_rsc_no), v);
+
+ /* Transferring size */
+ regmap_write(r, CDMBC_ITSTEPS(cipr_rsc_no), p->total_size);
+
+ /* For ring buffer */
+ regmap_write(r, CDMBC_RBBGNADRSD(cipr_rsc_no), cipr_sdram_start);
+ regmap_write(r, CDMBC_RBENDADRSD(cipr_rsc_no), cipr_sdram_end);
+ regmap_write(r, CDMBC_RBRDPTRD(cipr_rsc_no), cipr_sdram_start);
+ regmap_write(r, CDMBC_RBWRPTRD(cipr_rsc_no), cipr_sdram_end);
+
+ regmap_write(r, CDMBC_RBBGNADRSU(cipr_rsc_no), cipr_sdram_start >> 32);
+ regmap_write(r, CDMBC_RBENDADRSU(cipr_rsc_no), cipr_sdram_end >> 32);
+ regmap_write(r, CDMBC_RBRDPTRU(cipr_rsc_no), cipr_sdram_start >> 32);
+ regmap_write(r, CDMBC_RBWRPTRU(cipr_rsc_no), cipr_sdram_end >> 32);
+
+ regmap_write(r, CDMBC_RBBGNADRSD(cipw_rsc_no), cipw_sdram_start);
+ regmap_write(r, CDMBC_RBENDADRSD(cipw_rsc_no), cipw_sdram_end);
+ regmap_write(r, CDMBC_RBRDPTRD(cipw_rsc_no), cipw_sdram_end);
+ regmap_write(r, CDMBC_RBWRPTRD(cipw_rsc_no), cipw_sdram_start);
+
+ regmap_write(r, CDMBC_RBBGNADRSU(cipw_rsc_no), cipw_sdram_start >> 32);
+ regmap_write(r, CDMBC_RBENDADRSU(cipw_rsc_no), cipw_sdram_end >> 32);
+ regmap_write(r, CDMBC_RBRDPTRU(cipw_rsc_no), cipw_sdram_end >> 32);
+ regmap_write(r, CDMBC_RBWRPTRU(cipw_rsc_no), cipw_sdram_start >> 32);
+
+ /* Transfer settings */
+ regmap_write(r, CDMBC_CIPMODE(p->cip_file_ch),
+ (p->push) ? CDMBC_CIPMODE_PUSH : 0);
+
+ regmap_write(r, CDMBC_CIPPRIORITY(p->cip_file_ch),
+ FIELD_PREP(CDMBC_CIPPRIORITY_PRIOR_MASK, 3));
+}
+
+static void file_channel_start(struct hsc_chip *chip, int cip_file_ch,
+ bool push, bool mmu_en)
+{
+ struct regmap *r = chip->regmap;
+ int cipr_rsc_no, cipw_rsc_no;
+ int cipr_tdbp_no, cipw_tdbp_no;
+ u32 v = 0;
+
+ cipr_rsc_no = HSC_CIP_FILE_TO_CIPR_DMCH(cip_file_ch);
+ cipw_rsc_no = HSC_CIP_FILE_TO_CIPW_DMCH(cip_file_ch);
+ cipr_tdbp_no = HSC_CIP_FILE_TO_CIPR(cip_file_ch);
+ cipw_tdbp_no = HSC_CIP_FILE_TO_CIPW(cip_file_ch);
+
+ regmap_write(r, CDMBC_CIPMODE(cip_file_ch),
+ (push) ? CDMBC_CIPMODE_PUSH : 0);
+
+ if (mmu_en) {
+ v = CDMBC_CHDDR_REG_LOAD_ON | CDMBC_CHDDR_AT_CHEN_ON;
+
+ /* Enable IOMMU for CIP-R and CIP-W */
+ regmap_write(r, CDMBC_CHDDR(cipr_rsc_no),
+ v | CDMBC_CHDDR_SET_MCB_RD);
+ regmap_write(r, CDMBC_CHDDR(cipw_rsc_no),
+ v | CDMBC_CHDDR_SET_MCB_WR);
+ }
+
+ v = 0x01000000 | (1 << cipr_tdbp_no) | (1 << cipw_tdbp_no);
+ regmap_write(r, CDMBC_STRT(1), v);
+}
+
+static int ucode_load_dma(struct hsc_chip *chip, int mode)
+{
+ struct regmap *r = chip->regmap;
+ struct hsc_ucode_buf *ucode;
+ struct hsc_cip_file_dma_param dma_p = {0};
+ u32 cip_f_ctrl, v;
+
+ switch (mode) {
+ case HSC_UCODE_SPU_0:
+ case HSC_UCODE_SPU_1:
+ ucode = &chip->ucode_spu;
+ cip_f_ctrl = 0x2f090001;
+ break;
+ case HSC_UCODE_ACE:
+ ucode = &chip->ucode_am;
+ cip_f_ctrl = 0x3f090001;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_write(r, CIP_F_CTRL, cip_f_ctrl);
+
+ regmap_write(r, IOB_INTREN(HSC_INTR_IOB_2),
+ INTR2_CIP_AUTH_S |
+ INTR2_MBC_CIP_R(0) | INTR2_MBC_CIP_W(0));
+
+ dma_p.cip_file_ch = HSC_CIP_FILE_NO_0;
+ dma_p.cip_r_sdram = ucode->phys_code;
+ dma_p.cip_w_sdram = 0;
+ dma_p.inter_size = ucode->size_code;
+ dma_p.total_size = ucode->size_code;
+ dma_p.key_id1 = 0;
+ dma_p.key_id0 = 0;
+ dma_p.endian = 1;
+ dma_p.id1_en = 0;
+ file_channel_dma_set(chip, &dma_p);
+ file_channel_start(chip, HSC_CIP_FILE_NO_0, true, false);
+
+ do {
+ regmap_read(r, CDMBC_CHIR(HSC_MBC_DMCH_CIP0_R), &v);
+ msleep(20);
+ } while (!(v & INTR_MBC_CH_WDONE));
+ regmap_write(r, CDMBC_CHIR(HSC_MBC_DMCH_CIP0_R), v);
+
+ do {
+ regmap_read(r, CDMBC_CHIR(HSC_MBC_DMCH_CIP0_W), &v);
+ msleep(20);
+ } while (!(v & INTR_MBC_CH_WDONE));
+ regmap_write(r, CDMBC_CHIR(HSC_MBC_DMCH_CIP0_W), v);
+
+ regmap_read(r, CDMBC_RBIR(HSC_MBC_DMCH_CIP0_R), &v);
+ regmap_write(r, CDMBC_RBIR(HSC_MBC_DMCH_CIP0_R), v);
+
+ regmap_read(r, CDMBC_RBIR(HSC_MBC_DMCH_CIP0_W), &v);
+ regmap_write(r, CDMBC_RBIR(HSC_MBC_DMCH_CIP0_W), v);
+
+ /* Clear & disable interrupt */
+ regmap_read(r, IOB_INTRST(HSC_INTR_IOB_2), &v);
+ regmap_write(r, IOB_INTRST(HSC_INTR_IOB_2), v);
+
+ regmap_write(r, IOB_INTREN(HSC_INTR_IOB_2), 0);
+
+ return 0;
+}
+
+static int ucode_load(struct hsc_chip *chip, int mode)
+{
+ struct device *dev = &chip->pdev->dev;
+ const struct hsc_spec_ucode *spec;
+ struct hsc_ucode_buf *ucode;
+ const struct firmware *firm_code, *firm_data;
+ int ret;
+
+ switch (mode) {
+ case HSC_UCODE_SPU_0:
+ case HSC_UCODE_SPU_1:
+ spec = &chip->spec->ucode_spu;
+ ucode = &chip->ucode_spu;
+ break;
+ case HSC_UCODE_ACE:
+ spec = &chip->spec->ucode_ace;
+ ucode = &chip->ucode_am;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = request_firmware(&firm_code, spec->name_code, dev);
+ if (ret) {
+ dev_err(dev, "Failed to load firmware '%s'.\n",
+ spec->name_code);
+ return ret;
+ }
+
+ ret = request_firmware(&firm_data, spec->name_data, dev);
+ if (ret) {
+ dev_err(dev, "Failed to load firmware '%s'.\n",
+ spec->name_data);
+ goto err_firm_code;
+ }
+
+ ucode->buf_code = dma_alloc_coherent(dev, firm_code->size,
+ &ucode->phys_code, GFP_KERNEL);
+ if (!ucode->buf_code) {
+ ret = -ENOMEM;
+ goto err_firm_data;
+ }
+ ucode->size_code = firm_code->size;
+
+ ucode->buf_data = dma_alloc_coherent(dev, firm_data->size,
+ &ucode->phys_data, GFP_KERNEL);
+ if (!ucode->buf_data) {
+ ret = -ENOMEM;
+ goto err_buf_code;
+ }
+ ucode->size_data = firm_data->size;
+
+ memcpy(ucode->buf_code, firm_code->data, firm_code->size);
+ memcpy(ucode->buf_data, firm_data->data, firm_data->size);
+
+ ret = ucode_set_data_addr(chip, mode);
+ if (ret)
+ goto err_buf_data;
+
+ ret = ucode_load_dma(chip, mode);
+ if (ret)
+ goto err_buf_data;
+
+ release_firmware(firm_data);
+ release_firmware(firm_code);
+
+ return 0;
+
+err_buf_data:
+ dma_free_coherent(dev, ucode->size_data, ucode->buf_data,
+ ucode->phys_data);
+
+err_buf_code:
+ dma_free_coherent(dev, ucode->size_code, ucode->buf_code,
+ ucode->phys_code);
+
+err_firm_data:
+ release_firmware(firm_data);
+
+err_firm_code:
+ release_firmware(firm_code);
+
+ return ret;
+}
+
+static int ucode_unload(struct hsc_chip *chip, int mode)
+{
+ struct device *dev = &chip->pdev->dev;
+ struct hsc_ucode_buf *ucode;
+
+ switch (mode) {
+ case HSC_UCODE_SPU_0:
+ case HSC_UCODE_SPU_1:
+ ucode = &chip->ucode_spu;
+ break;
+ case HSC_UCODE_ACE:
+ ucode = &chip->ucode_am;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dma_free_coherent(dev, ucode->size_data, ucode->buf_data,
+ ucode->phys_data);
+ dma_free_coherent(dev, ucode->size_code, ucode->buf_code,
+ ucode->phys_code);
+
+ return 0;
+}
+
+int hsc_ucode_load_all(struct hsc_chip *chip)
+{
+ int ret;
+
+ core_start(chip);
+ core_clear_ram(chip);
+
+ ret = ucode_load(chip, HSC_UCODE_SPU_0);
+ if (ret)
+ return ret;
+ core_start_spu(chip);
+
+ ret = ucode_load(chip, HSC_UCODE_ACE);
+ if (ret)
+ return ret;
+ core_start_ap(chip);
+
+ return 0;
+}
+
+int hsc_ucode_unload_all(struct hsc_chip *chip)
+{
+ int ret;
+
+ core_stop(chip);
+
+ ret = ucode_unload(chip, HSC_UCODE_SPU_0);
+ if (ret)
+ return ret;
+
+ ret = ucode_unload(chip, HSC_UCODE_ACE);
+ if (ret)
+ return ret;
+
+ return 0;
+}
--
2.17.0