
On 07.03.2018 22:52, Marek Behún wrote:
The drivers are based on Linux driver by Gregory Clement.
The TBG clocks support only the .get_rate method.
- since setting rate is not supported, the driver computes the rates when probing and so subsequent calls to the .get_rate method do not read the corresponding registers again
The peripheral clocks support methods .get_rate, .enable and .disable.
the .set_parent method theoretically could be supported on some clocks (the parent would have to be one of the TBG clocks)
the .set_rate method would have to try all the divider values to find the best approximation of a given rate, and it doesn't seem like this should be needed in U-Boot, therefore not implemented
Signed-off-by: Marek Behun marek.behun@nic.cz
Looks good, so:
Reviewed-by: Stefan Roese sr@denx.de
Thanks, Stefan
arch/arm/dts/armada-37xx.dtsi | 20 ++ drivers/clk/Kconfig | 1 + drivers/clk/Makefile | 1 + drivers/clk/mvebu/Kconfig | 11 + drivers/clk/mvebu/Makefile | 1 + drivers/clk/mvebu/armada-37xx-periph.c | 464 +++++++++++++++++++++++++++++++++ drivers/clk/mvebu/armada-37xx-tbg.c | 153 +++++++++++ 7 files changed, 651 insertions(+)
diff --git a/arch/arm/dts/armada-37xx.dtsi b/arch/arm/dts/armada-37xx.dtsi index d0529637f4..e848812fca 100644 --- a/arch/arm/dts/armada-37xx.dtsi +++ b/arch/arm/dts/armada-37xx.dtsi @@ -106,6 +106,26 @@ status = "disabled"; };
nb_periph_clk: nb-periph-clk@13000 {
compatible = "marvell,armada-3700-periph-clock-nb";
reg = <0x13000 0x100>;
clocks = <&tbg 0>, <&tbg 1>, <&tbg 2>, <&tbg 3>;
#clock-cells = <1>;
};
sb_periph_clk: sb-periph-clk@18000 {
compatible = "marvell,armada-3700-periph-clock-sb";
reg = <0x18000 0x100>;
clocks = <&tbg 0>, <&tbg 1>, <&tbg 2>, <&tbg 3>;
#clock-cells = <1>;
};
tbg: tbg@13200 {
compatible = "marvell,armada-3700-tbg-clock";
reg = <0x13200 0x100>;
#clock-cells = <1>;
};
pinctrl_nb: pinctrl-nb@13800 { compatible = "marvell,armada3710-nb-pinctrl", "syscon", "simple-mfd";
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index cdfa052c16..a40c8e5c8f 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -80,5 +80,6 @@ source "drivers/clk/uniphier/Kconfig" source "drivers/clk/exynos/Kconfig" source "drivers/clk/at91/Kconfig" source "drivers/clk/renesas/Kconfig" +source "drivers/clk/mvebu/Kconfig"
endmenu diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index dab106ab7f..094bcf5847 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -11,6 +11,7 @@ obj-y += tegra/ obj-$(CONFIG_ARCH_ASPEED) += aspeed/ obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/ obj-$(CONFIG_CLK_AT91) += at91/ +obj-$(CONFIG_CLK_MVEBU) += mvebu/ obj-$(CONFIG_CLK_BCM6345) += clk_bcm6345.o obj-$(CONFIG_CLK_BOSTON) += clk_boston.o obj-$(CONFIG_CLK_EXYNOS) += exynos/ diff --git a/drivers/clk/mvebu/Kconfig b/drivers/clk/mvebu/Kconfig new file mode 100644 index 0000000000..e776a15e7b --- /dev/null +++ b/drivers/clk/mvebu/Kconfig @@ -0,0 +1,11 @@ +config CLK_MVEBU
- bool "MVEBU clock drivers"
- depends on CLK && ARCH_MVEBU
- help
Enable support for clock present on Marvell MVEBU SoCs.
+config CLK_ARMADA_3720
- bool "Marvell Armada 3720 clock driver"
- depends on CLK_MVEBU && ARM64
- help
Enable this to support the clocks on Marvell Armada 3720 SoC.
diff --git a/drivers/clk/mvebu/Makefile b/drivers/clk/mvebu/Makefile new file mode 100644 index 0000000000..7f80313203 --- /dev/null +++ b/drivers/clk/mvebu/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_CLK_ARMADA_3720) += armada-37xx-periph.o armada-37xx-tbg.o diff --git a/drivers/clk/mvebu/armada-37xx-periph.c b/drivers/clk/mvebu/armada-37xx-periph.c new file mode 100644 index 0000000000..94eabe5f19 --- /dev/null +++ b/drivers/clk/mvebu/armada-37xx-periph.c @@ -0,0 +1,464 @@ +/*
- Marvell Armada 37xx SoC Peripheral clocks
- Marek Behun marek.behun@nic.cz
- Based on Linux driver by:
- Gregory CLEMENT gregory.clement@free-electrons.com
- SPDX-License-Identifier: GPL-2.0+
- */
+#include <common.h> +#include <malloc.h> +#include <clk-uclass.h> +#include <clk.h> +#include <dm.h> +#include <asm/io.h> +#include <asm/arch/cpu.h>
+#define TBG_SEL 0x0 +#define DIV_SEL0 0x4 +#define DIV_SEL1 0x8 +#define DIV_SEL2 0xC +#define CLK_SEL 0x10 +#define CLK_DIS 0x14
+enum a37xx_periph_parent {
- TBG_A_P = 0,
- TBG_B_P = 1,
- TBG_A_S = 2,
- TBG_B_S = 3,
- MAX_TBG_PARENTS = 4,
- XTAL = 4,
- MAX_PARENTS = 5,
+};
+static const struct {
- const char *name;
- enum a37xx_periph_parent parent;
+} a37xx_periph_parent_names[] = {
- { "TBG-A-P", TBG_A_P },
- { "TBG-B-P", TBG_B_P },
- { "TBG-A-S", TBG_A_S },
- { "TBG-B-S", TBG_B_S },
- { "xtal" , XTAL },
+};
+struct clk_periph;
+struct a37xx_periphclk {
- void __iomem *reg;
- ulong parents[MAX_PARENTS];
- const struct clk_periph *clks;
- bool clk_has_periph_parent[16];
- int clk_parent[16];
- int count;
+};
+struct clk_div_table {
- u32 div;
- u32 val;
+};
+struct clk_periph {
- const char *name;
- const char *parent_name;
- u32 disable_bit;
- int mux_shift;
- const struct clk_div_table *div_table[2];
- s32 div_reg_off[2];
- u32 div_mask[2];
- int div_shift[2];
- unsigned can_gate : 1;
- unsigned can_mux : 1;
- unsigned dividers : 2;
+};
+static const struct clk_div_table div_table1[] = {
- { 1, 1 },
- { 2, 2 },
- { 0, 0 },
+};
+static const struct clk_div_table div_table2[] = {
- { 2, 1 },
- { 4, 2 },
- { 0, 0 },
+};
+static const struct clk_div_table div_table6[] = {
- { 1, 1 },
- { 2, 2 },
- { 3, 3 },
- { 4, 4 },
- { 5, 5 },
- { 6, 6 },
- { 0, 0 },
+};
+#define CLK_FULL_DD(_n, _d, _mux, _r0, _r1, _s0, _s1) \
- { \
.name = #_n, \
.disable_bit = BIT(_d), \
.mux_shift = _mux, \
.div_table[0] = div_table6, \
.div_table[1] = div_table6, \
.div_reg_off[0] = _r0, \
.div_reg_off[1] = _r1, \
.div_shift[0] = _s0, \
.div_shift[1] = _s1, \
.div_mask[0] = 7, \
.div_mask[1] = 7, \
.can_gate = 1, \
.can_mux = 1, \
.dividers = 2, \
- }
+#define CLK_FULL(_n, _d, _mux, _r, _s, _m, _t) \
- { \
.name = #_n, \
.disable_bit = BIT(_d), \
.mux_shift = _mux, \
.div_table[0] = _t, \
.div_reg_off[0] = _r, \
.div_shift[0] = _s, \
.div_mask[0] = _m, \
.can_gate = 1, \
.can_mux = 1, \
.dividers = 1, \
- }
+#define CLK_GATE_DIV(_n, _d, _r, _s, _m, _t, _p) \
- { \
.name = #_n, \
.parent_name = _p, \
.disable_bit = BIT(_d), \
.div_table[0] = _t, \
.div_reg_off[0] = _r, \
.div_shift[0] = _s, \
.div_mask[0] = _m, \
.can_gate = 1, \
.dividers = 1, \
- }
+#define CLK_GATE(_n, _d, _p) \
- { \
.name = #_n, \
.parent_name = _p, \
.disable_bit = BIT(_d), \
.can_gate = 1, \
- }
+#define CLK_MUX_DIV(_n, _mux, _r, _s, _m, _t) \
- { \
.name = #_n, \
.mux_shift = _mux, \
.div_table[0] = _t, \
.div_reg_off[0] = _r, \
.div_shift[0] = _s, \
.div_mask[0] = _m, \
.can_mux = 1, \
.dividers = 1, \
- }
+#define CLK_MUX_DD(_n, _mux, _r0, _r1, _s0, _s1) \
- { \
.name = #_n, \
.mux_shift = _mux, \
.div_table[0] = div_table6, \
.div_table[1] = div_table6, \
.div_reg_off[0] = _r0, \
.div_reg_off[1] = _r1, \
.div_shift[0] = _s0, \
.div_shift[1] = _s1, \
.div_mask[0] = 7, \
.div_mask[1] = 7, \
.can_mux = 1, \
.dividers = 2, \
- }
+/* NB periph clocks */ +static const struct clk_periph clks_nb[] ={
- CLK_FULL_DD(mmc, 2, 0, DIV_SEL2, DIV_SEL2, 16, 13),
- CLK_FULL_DD(sata_host, 3, 2, DIV_SEL2, DIV_SEL2, 10, 7),
- CLK_FULL_DD(sec_at, 6, 4, DIV_SEL1, DIV_SEL1, 3, 0),
- CLK_FULL_DD(sec_dap, 7, 6, DIV_SEL1, DIV_SEL1, 9, 6),
- CLK_FULL_DD(tscem, 8, 8, DIV_SEL1, DIV_SEL1, 15, 12),
- CLK_FULL(tscem_tmx, 10, 10, DIV_SEL1, 18, 7, div_table6),
- CLK_GATE(avs, 11, "xtal"),
- CLK_FULL_DD(sqf, 12, 12, DIV_SEL1, DIV_SEL1, 27, 24),
- CLK_FULL_DD(pwm, 13, 14, DIV_SEL0, DIV_SEL0, 3, 0),
- CLK_GATE(i2c_2, 16, "xtal"),
- CLK_GATE(i2c_1, 17, "xtal"),
- CLK_GATE_DIV(ddr_phy, 19, DIV_SEL0, 18, 1, div_table2, "TBG-A-S"),
- CLK_FULL_DD(ddr_fclk, 21, 16, DIV_SEL0, DIV_SEL0, 15, 12),
- CLK_FULL(trace, 22, 18, DIV_SEL0, 20, 7, div_table6),
- CLK_FULL(counter, 23, 20, DIV_SEL0, 23, 7, div_table6),
- CLK_FULL_DD(eip97, 24, 24, DIV_SEL2, DIV_SEL2, 22, 19),
- CLK_MUX_DIV(cpu, 22, DIV_SEL0, 28, 7, div_table6),
- { },
+};
+/* SB periph clocks */ +static const struct clk_periph clks_sb[] = {
- CLK_MUX_DD(gbe_50, 6, DIV_SEL2, DIV_SEL2, 6, 9),
- CLK_MUX_DD(gbe_core, 8, DIV_SEL1, DIV_SEL1, 18, 21),
- CLK_MUX_DD(gbe_125, 10, DIV_SEL1, DIV_SEL1, 6, 9),
- CLK_GATE(gbe1_50, 0, "gbe_50"),
- CLK_GATE(gbe0_50, 1, "gbe_50"),
- CLK_GATE(gbe1_125, 2, "gbe_125"),
- CLK_GATE(gbe0_125, 3, "gbe_125"),
- CLK_GATE_DIV(gbe1_core, 4, DIV_SEL1, 13, 1, div_table1, "gbe_core"),
- CLK_GATE_DIV(gbe0_core, 5, DIV_SEL1, 14, 1, div_table1, "gbe_core"),
- CLK_GATE_DIV(gbe_bm, 12, DIV_SEL1, 0, 1, div_table1, "gbe_core"),
- CLK_FULL_DD(sdio, 11, 14, DIV_SEL0, DIV_SEL0, 3, 6),
- CLK_FULL_DD(usb32_usb2_sys, 16, 16, DIV_SEL0, DIV_SEL0, 9, 12),
- CLK_FULL_DD(usb32_ss_sys, 17, 18, DIV_SEL0, DIV_SEL0, 15, 18),
- { },
+};
+static inline int get_mux(struct a37xx_periphclk *priv, int shift) +{
- return (readl(priv->reg + TBG_SEL) >> shift) & 3;
+}
+static ulong periph_clk_get_rate(struct a37xx_periphclk *priv, int id);
+static ulong get_parent_rate(struct a37xx_periphclk *priv, int id) +{
- const struct clk_periph *clk = &priv->clks[id];
- if (clk->can_mux) {
/* parent is one of TBG clocks */
int tbg = get_mux(priv, clk->mux_shift);
return priv->parents[tbg];
- } else if (priv->clk_has_periph_parent[id]) {
/* parent is one of other periph clocks */
if (priv->clk_parent[id] >= priv->count)
return -EINVAL;
return periph_clk_get_rate(priv, priv->clk_parent[id]);
- } else {
/* otherwise parent is one of TBGs or XTAL */
if (priv->clk_parent[id] >= MAX_PARENTS)
return -EINVAL;
return priv->parents[priv->clk_parent[id]];
- }
+}
+static ulong get_div(struct a37xx_periphclk *priv,
const struct clk_periph *clk, int idx)
+{
- const struct clk_div_table *i;
- u32 reg;
- reg = readl(priv->reg + clk->div_reg_off[idx]);
- reg = (reg >> clk->div_shift[idx]) & clk->div_mask[idx];
- /* find divisor for register value val */
- for (i = clk->div_table[idx]; i && i->div != 0; ++i)
if (i->val == reg)
return i->div;
- return 0;
+}
+static ulong periph_clk_get_rate(struct a37xx_periphclk *priv, int id) +{
- const struct clk_periph *clk = &priv->clks[id];
- ulong rate, div;
- int i;
- rate = get_parent_rate(priv, id);
- if (rate == -EINVAL)
return -EINVAL;
- /* divide the parent rate by dividers */
- div = 1;
- for (i = 0; i < clk->dividers; ++i)
div *= get_div(priv, clk, i);
- if (!div)
return 0;
- return DIV_ROUND_UP(rate, div);
+}
+static ulong armada_37xx_periph_clk_get_rate(struct clk *clk) +{
- struct a37xx_periphclk *priv = dev_get_priv(clk->dev);
- if (clk->id >= priv->count)
return -EINVAL;
- return periph_clk_get_rate(priv, clk->id);
+}
+static int periph_clk_enable(struct clk *clk, int enable) +{
- struct a37xx_periphclk *priv = dev_get_priv(clk->dev);
- const struct clk_periph *periph_clk = &priv->clks[clk->id];
- if (clk->id >= priv->count)
return -EINVAL;
- if (!periph_clk->can_gate)
return -ENOTSUPP;
- if (enable)
clrbits_le32(priv->reg + CLK_DIS, periph_clk->disable_bit);
- else
setbits_le32(priv->reg + CLK_DIS, periph_clk->disable_bit);
- return 0;
+}
+static int armada_37xx_periph_clk_enable(struct clk *clk) +{
- return periph_clk_enable(clk, 1);
+}
+static int armada_37xx_periph_clk_disable(struct clk *clk) +{
- return periph_clk_enable(clk, 0);
+}
+int armada_37xx_periph_clk_dump(struct udevice *dev) +{
- struct a37xx_periphclk *priv = dev_get_priv(dev);
- const struct clk_periph *clks;
- int i;
- if (!priv)
return -ENODEV;
- clks = priv->clks;
- for (i = 0; i < priv->count; ++i)
printf(" %s at %lu Hz\n", clks[i].name,
periph_clk_get_rate(priv, i));
- printf("\n");
- return 0;
+}
+static int armada_37xx_periph_clk_probe(struct udevice *dev) +{
- struct a37xx_periphclk *priv = dev_get_priv(dev);
- const struct clk_periph *clks;
- int ret, i;
- clks = (const struct clk_periph *) dev_get_driver_data(dev);
- if (!clks)
return -ENODEV;
- priv->reg = dev_read_addr_ptr(dev);
- if (!priv->reg) {
dev_err(dev, "no io address\n");
return -ENODEV;
- }
- /* count clk_periph nodes */
- priv->count = 0;
- while (clks[priv->count].name)
priv->count++;
- priv->clks = clks;
- /* assign parent IDs to nodes which have non-NULL parent_name */
- for (i = 0; i < priv->count; ++i) {
int j;
if (!clks[i].parent_name)
continue;
/* first try if parent_name is one of TBGs or XTAL */
for (j = 0; j < MAX_PARENTS; ++j)
if (!strcmp(clks[i].parent_name,
a37xx_periph_parent_names[j].name))
break;
if (j < MAX_PARENTS) {
priv->clk_has_periph_parent[i] = false;
priv->clk_parent[i] =
a37xx_periph_parent_names[j].parent;
continue;
}
/* else parent_name should be one of other periph clocks */
for (j = 0; j < priv->count; ++j) {
if (!strcmp(clks[i].parent_name, clks[j].name))
break;
}
if (j < priv->count) {
priv->clk_has_periph_parent[i] = true;
priv->clk_parent[i] = j;
continue;
}
dev_err(dev, "undefined parent %s\n", clks[i].parent_name);
return -EINVAL;
- }
- for (i = 0; i < MAX_PARENTS; ++i) {
struct clk clk;
if (i == XTAL) {
priv->parents[i] = get_ref_clk() * 1000000;
continue;
}
ret = clk_get_by_index(dev, i, &clk);
if (ret) {
dev_err(dev, "one of parent clocks (%i) missing: %i\n",
i, ret);
return -ENODEV;
}
priv->parents[i] = clk_get_rate(&clk);
clk_free(&clk);
- }
- return 0;
+}
+static const struct clk_ops armada_37xx_periph_clk_ops = {
- .get_rate = armada_37xx_periph_clk_get_rate,
- .enable = armada_37xx_periph_clk_enable,
- .disable = armada_37xx_periph_clk_disable,
+};
+static const struct udevice_id armada_37xx_periph_clk_ids[] = {
- {
.compatible = "marvell,armada-3700-periph-clock-nb",
.data = (ulong) clks_nb,
- },
- {
.compatible = "marvell,armada-3700-periph-clock-sb",
.data = (ulong) clks_sb,
- },
- {}
+};
+U_BOOT_DRIVER(armada_37xx_periph_clk) = {
- .name = "armada_37xx_periph_clk",
- .id = UCLASS_CLK,
- .of_match = armada_37xx_periph_clk_ids,
- .ops = &armada_37xx_periph_clk_ops,
- .priv_auto_alloc_size = sizeof(struct a37xx_periphclk),
- .probe = armada_37xx_periph_clk_probe,
+}; diff --git a/drivers/clk/mvebu/armada-37xx-tbg.c b/drivers/clk/mvebu/armada-37xx-tbg.c new file mode 100644 index 0000000000..e8c654f1a6 --- /dev/null +++ b/drivers/clk/mvebu/armada-37xx-tbg.c @@ -0,0 +1,153 @@ +/*
- Marvell Armada 37xx SoC Time Base Generator clocks
- Marek Behun marek.behun@nic.cz
- Based on Linux driver by:
- Gregory CLEMENT gregory.clement@free-electrons.com
- SPDX-License-Identifier: GPL-2.0+
- */
+#include <common.h> +#include <clk-uclass.h> +#include <clk.h> +#include <dm.h> +#include <asm/io.h> +#include <asm/arch/cpu.h>
+#define NUM_TBG 4
+#define TBG_CTRL0 0x4 +#define TBG_CTRL1 0x8 +#define TBG_CTRL7 0x20 +#define TBG_CTRL8 0x30
+#define TBG_DIV_MASK 0x1FF
+#define TBG_A_REFDIV 0 +#define TBG_B_REFDIV 16
+#define TBG_A_FBDIV 2 +#define TBG_B_FBDIV 18
+#define TBG_A_VCODIV_SE 0 +#define TBG_B_VCODIV_SE 16
+#define TBG_A_VCODIV_DIFF 1 +#define TBG_B_VCODIV_DIFF 17
+struct tbg_def {
- const char *name;
- u32 refdiv_offset;
- u32 fbdiv_offset;
- u32 vcodiv_reg;
- u32 vcodiv_offset;
+};
+static const struct tbg_def tbg[NUM_TBG] = {
- {"TBG-A-P", TBG_A_REFDIV, TBG_A_FBDIV, TBG_CTRL8, TBG_A_VCODIV_DIFF},
- {"TBG-B-P", TBG_B_REFDIV, TBG_B_FBDIV, TBG_CTRL8, TBG_B_VCODIV_DIFF},
- {"TBG-A-S", TBG_A_REFDIV, TBG_A_FBDIV, TBG_CTRL1, TBG_A_VCODIV_SE},
- {"TBG-B-S", TBG_B_REFDIV, TBG_B_FBDIV, TBG_CTRL1, TBG_B_VCODIV_SE},
+};
+struct a37xx_tbgclk {
- ulong rates[NUM_TBG];
- unsigned int mult[NUM_TBG];
- unsigned int div[NUM_TBG];
+};
+static unsigned int tbg_get_mult(void __iomem *reg, const struct tbg_def *ptbg) +{
- u32 val;
- val = readl(reg + TBG_CTRL0);
- return ((val >> ptbg->fbdiv_offset) & TBG_DIV_MASK) << 2;
+}
+static unsigned int tbg_get_div(void __iomem *reg, const struct tbg_def *ptbg) +{
- u32 val;
- unsigned int div;
- val = readl(reg + TBG_CTRL7);
- div = (val >> ptbg->refdiv_offset) & TBG_DIV_MASK;
- if (div == 0)
div = 1;
- val = readl(reg + ptbg->vcodiv_reg);
- div *= 1 << ((val >> ptbg->vcodiv_offset) & TBG_DIV_MASK);
- return div;
+}
+static ulong armada_37xx_tbg_clk_get_rate(struct clk *clk) +{
- struct a37xx_tbgclk *priv = dev_get_priv(clk->dev);
- if (clk->id >= NUM_TBG)
return -ENODEV;
- return priv->rates[clk->id];
+}
+int armada_37xx_tbg_clk_dump(struct udevice *dev) +{
- struct a37xx_tbgclk *priv = dev_get_priv(dev);
- int i;
- for (i = 0; i < NUM_TBG; ++i)
printf(" %s at %lu Hz\n", tbg[i].name,
priv->rates[i]);
- printf("\n");
- return 0;
+}
+static int armada_37xx_tbg_clk_probe(struct udevice *dev) +{
- struct a37xx_tbgclk *priv = dev_get_priv(dev);
- void __iomem *reg;
- ulong xtal;
- int i;
- reg = dev_read_addr_ptr(dev);
- if (!reg) {
dev_err(dev, "no io address\n");
return -ENODEV;
- }
- xtal = (ulong) get_ref_clk() * 1000000;
- for (i = 0; i < NUM_TBG; ++i) {
unsigned int mult, div;
mult = tbg_get_mult(reg, &tbg[i]);
div = tbg_get_div(reg, &tbg[i]);
priv->rates[i] = (xtal * mult) / div;
- }
- return 0;
+}
+static const struct clk_ops armada_37xx_tbg_clk_ops = {
- .get_rate = armada_37xx_tbg_clk_get_rate,
+};
+static const struct udevice_id armada_37xx_tbg_clk_ids[] = {
- { .compatible = "marvell,armada-3700-tbg-clock" },
- {}
+};
+U_BOOT_DRIVER(armada_37xx_tbg_clk) = {
- .name = "armada_37xx_tbg_clk",
- .id = UCLASS_CLK,
- .of_match = armada_37xx_tbg_clk_ids,
- .ops = &armada_37xx_tbg_clk_ops,
- .priv_auto_alloc_size = sizeof(struct a37xx_tbgclk),
- .probe = armada_37xx_tbg_clk_probe,
+};
Viele Grüße, Stefan