From 996e39bb03ba0ec31dbe8c1c7df11130ba9de639 Mon Sep 17 00:00:00 2001 From: Freeman Liu Date: Mon, 28 Jan 2019 15:54:54 +0000 Subject: nvmem: sc27xx: Convert nvmem offset to block index The Spreadtrum SC27XX efuse data are organized by blocks and each block contains 2 bytes data. Moreover the nvmem core always pass the offset in byte to the controller, so we should change the offset in byte to the correct block index and block offset to read the data. Signed-off-by: Freeman Liu Signed-off-by: Baolin Wang Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/sc27xx-efuse.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'drivers/nvmem') diff --git a/drivers/nvmem/sc27xx-efuse.c b/drivers/nvmem/sc27xx-efuse.c index 33185d8d82cf..c6ee21018d80 100644 --- a/drivers/nvmem/sc27xx-efuse.c +++ b/drivers/nvmem/sc27xx-efuse.c @@ -106,10 +106,12 @@ static int sc27xx_efuse_poll_status(struct sc27xx_efuse *efuse, u32 bits) static int sc27xx_efuse_read(void *context, u32 offset, void *val, size_t bytes) { struct sc27xx_efuse *efuse = context; - u32 buf; + u32 buf, blk_index = offset / SC27XX_EFUSE_BLOCK_WIDTH; + u32 blk_offset = (offset % SC27XX_EFUSE_BLOCK_WIDTH) * BITS_PER_BYTE; int ret; - if (offset > SC27XX_EFUSE_BLOCK_MAX || bytes > SC27XX_EFUSE_BLOCK_WIDTH) + if (blk_index > SC27XX_EFUSE_BLOCK_MAX || + bytes > SC27XX_EFUSE_BLOCK_WIDTH) return -EINVAL; ret = sc27xx_efuse_lock(efuse); @@ -133,7 +135,7 @@ static int sc27xx_efuse_read(void *context, u32 offset, void *val, size_t bytes) /* Set the block address to be read. */ ret = regmap_write(efuse->regmap, efuse->base + SC27XX_EFUSE_BLOCK_INDEX, - offset & SC27XX_EFUSE_BLOCK_MASK); + blk_index & SC27XX_EFUSE_BLOCK_MASK); if (ret) goto disable_efuse; @@ -171,8 +173,10 @@ disable_efuse: unlock_efuse: sc27xx_efuse_unlock(efuse); - if (!ret) + if (!ret) { + buf >>= blk_offset; memcpy(val, &buf, bytes); + } return ret; } -- cgit From 1694155573702cb11e59535e4e56478afda7493f Mon Sep 17 00:00:00 2001 From: Srinath Mannam Date: Mon, 28 Jan 2019 15:54:55 +0000 Subject: nvmem: bcm-ocotp: Add ACPI support to BCM OCOTP Add ACPI support to bcm ocotp driver This patch is based on Linux-4.20-rc7. Signed-off-by: Srinath Mannam Acked-by: Scott Branden Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/bcm-ocotp.c | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) (limited to 'drivers/nvmem') diff --git a/drivers/nvmem/bcm-ocotp.c b/drivers/nvmem/bcm-ocotp.c index 4159b3f41d79..a8097511582a 100644 --- a/drivers/nvmem/bcm-ocotp.c +++ b/drivers/nvmem/bcm-ocotp.c @@ -11,13 +11,14 @@ * GNU General Public License for more details. */ +#include #include #include #include #include #include #include -#include +#include #include /* @@ -78,9 +79,9 @@ static struct otpc_map otp_map_v2 = { }; struct otpc_priv { - struct device *dev; - void __iomem *base; - struct otpc_map *map; + struct device *dev; + void __iomem *base; + const struct otpc_map *map; struct nvmem_config *config; }; @@ -237,16 +238,22 @@ static struct nvmem_config bcm_otpc_nvmem_config = { }; static const struct of_device_id bcm_otpc_dt_ids[] = { - { .compatible = "brcm,ocotp" }, - { .compatible = "brcm,ocotp-v2" }, + { .compatible = "brcm,ocotp", .data = &otp_map }, + { .compatible = "brcm,ocotp-v2", .data = &otp_map_v2 }, { }, }; MODULE_DEVICE_TABLE(of, bcm_otpc_dt_ids); +static const struct acpi_device_id bcm_otpc_acpi_ids[] = { + { .id = "BRCM0700", .driver_data = (kernel_ulong_t)&otp_map }, + { .id = "BRCM0701", .driver_data = (kernel_ulong_t)&otp_map_v2 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(acpi, bcm_otpc_acpi_ids); + static int bcm_otpc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct device_node *dn = dev->of_node; struct resource *res; struct otpc_priv *priv; struct nvmem_device *nvmem; @@ -257,14 +264,9 @@ static int bcm_otpc_probe(struct platform_device *pdev) if (!priv) return -ENOMEM; - if (of_device_is_compatible(dev->of_node, "brcm,ocotp")) - priv->map = &otp_map; - else if (of_device_is_compatible(dev->of_node, "brcm,ocotp-v2")) - priv->map = &otp_map_v2; - else { - dev_err(dev, "%s otpc config map not defined\n", __func__); - return -EINVAL; - } + priv->map = device_get_match_data(dev); + if (!priv->map) + return -ENODEV; /* Get OTP base address register. */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -281,7 +283,7 @@ static int bcm_otpc_probe(struct platform_device *pdev) reset_start_bit(priv->base); /* Read size of memory in words. */ - err = of_property_read_u32(dn, "brcm,ocotp-size", &num_words); + err = device_property_read_u32(dev, "brcm,ocotp-size", &num_words); if (err) { dev_err(dev, "size parameter not specified\n"); return -EINVAL; @@ -294,7 +296,7 @@ static int bcm_otpc_probe(struct platform_device *pdev) bcm_otpc_nvmem_config.dev = dev; bcm_otpc_nvmem_config.priv = priv; - if (of_device_is_compatible(dev->of_node, "brcm,ocotp-v2")) { + if (priv->map == &otp_map_v2) { bcm_otpc_nvmem_config.word_size = 8; bcm_otpc_nvmem_config.stride = 8; } @@ -315,6 +317,7 @@ static struct platform_driver bcm_otpc_driver = { .driver = { .name = "brcm-otpc", .of_match_table = bcm_otpc_dt_ids, + .acpi_match_table = ACPI_PTR(bcm_otpc_acpi_ids), }, }; module_platform_driver(bcm_otpc_driver); -- cgit From c8b63ddc91190d4022d456bf6abbc2085b4f56b5 Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Mon, 28 Jan 2019 15:54:57 +0000 Subject: nvmem: imx-ocotp: add i.MX7ULP support i.MX7ULP is a new SoC of i.MX family which has 8 kbit eFuse OTP, enable ocotp driver support for this SoC. Signed-off-by: Anson Huang Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/imx-ocotp.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers/nvmem') diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c index afb429a417fe..90a398252770 100644 --- a/drivers/nvmem/imx-ocotp.c +++ b/drivers/nvmem/imx-ocotp.c @@ -433,6 +433,11 @@ static const struct ocotp_params imx7d_params = { .set_timing = imx_ocotp_set_imx7_timing, }; +static const struct ocotp_params imx7ulp_params = { + .nregs = 256, + .bank_address_words = 0, +}; + static const struct of_device_id imx_ocotp_dt_ids[] = { { .compatible = "fsl,imx6q-ocotp", .data = &imx6q_params }, { .compatible = "fsl,imx6sl-ocotp", .data = &imx6sl_params }, @@ -440,6 +445,7 @@ static const struct of_device_id imx_ocotp_dt_ids[] = { { .compatible = "fsl,imx6ul-ocotp", .data = &imx6ul_params }, { .compatible = "fsl,imx7d-ocotp", .data = &imx7d_params }, { .compatible = "fsl,imx6sll-ocotp", .data = &imx6sll_params }, + { .compatible = "fsl,imx7ulp-ocotp", .data = &imx7ulp_params }, { }, }; MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids); -- cgit From ffbc34bf0e9c66fd4c64a78ffd7bdec3de388387 Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Mon, 28 Jan 2019 15:54:59 +0000 Subject: nvmem: imx-ocotp: Implement i.MX6ULL/ULZ support In comparision to the i.MX6UL the lower cost variants i.MX6ULL/ULZ only supports 8 OTP banks a 8 words. Signed-off-by: Stefan Wahren Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/imx-ocotp.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers/nvmem') diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c index 90a398252770..08a9b1ef8ae4 100644 --- a/drivers/nvmem/imx-ocotp.c +++ b/drivers/nvmem/imx-ocotp.c @@ -427,6 +427,12 @@ static const struct ocotp_params imx6ul_params = { .set_timing = imx_ocotp_set_imx6_timing, }; +static const struct ocotp_params imx6ull_params = { + .nregs = 64, + .bank_address_words = 0, + .set_timing = imx_ocotp_set_imx6_timing, +}; + static const struct ocotp_params imx7d_params = { .nregs = 64, .bank_address_words = 4, @@ -443,6 +449,7 @@ static const struct of_device_id imx_ocotp_dt_ids[] = { { .compatible = "fsl,imx6sl-ocotp", .data = &imx6sl_params }, { .compatible = "fsl,imx6sx-ocotp", .data = &imx6sx_params }, { .compatible = "fsl,imx6ul-ocotp", .data = &imx6ul_params }, + { .compatible = "fsl,imx6ull-ocotp", .data = &imx6ull_params }, { .compatible = "fsl,imx7d-ocotp", .data = &imx7d_params }, { .compatible = "fsl,imx6sll-ocotp", .data = &imx6sll_params }, { .compatible = "fsl,imx7ulp-ocotp", .data = &imx7ulp_params }, -- cgit From 1716cfe8be4974741e56529aa7076f244aed9ade Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Mon, 28 Jan 2019 15:55:00 +0000 Subject: nvmem: core: Set the provider read-only when no write callback is given If no write callback is given the device should be marked as read-only. While at it also move from a bit or to a logical or as that is a logical expression. Signed-off-by: Alban Bedel Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/nvmem') diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index f7301bb4ef3b..cf2e1091fe89 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -646,8 +646,8 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) config->name ? config->id : nvmem->id); } - nvmem->read_only = device_property_present(config->dev, "read-only") | - config->read_only; + nvmem->read_only = device_property_present(config->dev, "read-only") || + config->read_only || !nvmem->reg_write; if (config->root_only) nvmem->dev.groups = nvmem->read_only ? -- cgit From 5087cc198e545ff9974b20daf2d43658f7711aa0 Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Mon, 28 Jan 2019 15:55:01 +0000 Subject: nvmem: core: Fix of_nvmem_cell_get() for optional cells of_nvmem_cell_get() should return -ENOENT when a cell isn't defined, otherwise callers can't distinguish between a missing cell and other errors. Signed-off-by: Alban Bedel Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/nvmem') diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index cf2e1091fe89..f8c43da6f2ca 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -1031,7 +1031,7 @@ struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, const char *id) cell_np = of_parse_phandle(np, "nvmem-cells", index); if (!cell_np) - return ERR_PTR(-EINVAL); + return ERR_PTR(-ENOENT); nvmem_np = of_get_next_parent(cell_np); if (!nvmem_np) -- cgit From 1c83267473b881cf75247acf1129456ef33aba86 Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Mon, 28 Jan 2019 15:55:02 +0000 Subject: nvmem: core: Fix cell lookup when no cell is found If the cell list is not empty and nvmem_find_cell_by_node/name() is called for a cell that is not present in the list they will return an invalid pointer instead of NULL. This happen because list_for_each_entry() stop once it reach the list head again, but as the list head is not contained in a struct nvmem_cell the iteration variable then contains an invalid value. This is easily solved by using a variable to iterate over the list and one to return the cell found. Signed-off-by: Alban Bedel Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/core.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'drivers/nvmem') diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index f8c43da6f2ca..a2ad44104aa2 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -525,12 +525,14 @@ out: static struct nvmem_cell * nvmem_find_cell_by_name(struct nvmem_device *nvmem, const char *cell_id) { - struct nvmem_cell *cell = NULL; + struct nvmem_cell *iter, *cell = NULL; mutex_lock(&nvmem_mutex); - list_for_each_entry(cell, &nvmem->cells, node) { - if (strcmp(cell_id, cell->name) == 0) + list_for_each_entry(iter, &nvmem->cells, node) { + if (strcmp(cell_id, iter->name) == 0) { + cell = iter; break; + } } mutex_unlock(&nvmem_mutex); @@ -994,12 +996,14 @@ out: static struct nvmem_cell * nvmem_find_cell_by_node(struct nvmem_device *nvmem, struct device_node *np) { - struct nvmem_cell *cell = NULL; + struct nvmem_cell *iter, *cell = NULL; mutex_lock(&nvmem_mutex); - list_for_each_entry(cell, &nvmem->cells, node) { - if (np == cell->np) + list_for_each_entry(iter, &nvmem->cells, node) { + if (np == iter->np) { + cell = iter; break; + } } mutex_unlock(&nvmem_mutex); -- cgit From d4e7fef1a9a5d2bf3f859ba9f50f4d5409a09ab0 Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Mon, 28 Jan 2019 15:55:03 +0000 Subject: nvmem: core: Properly handle connection ID in of_nvmem_device_get() of_nvmem_device_get() would crash if NULL was passed as a connection ID. Rework this to use the usual sementic of assuming the first connection when no connection ID is given. Furthermore of_nvmem_device_get() would return -EINVAL when it failed to resolve the connection, making it impossible to properly implement an optional connection. Return -ENOENT instead to let the caller know that the connection doesn't exists. Signed-off-by: Alban Bedel Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/core.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers/nvmem') diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index a2ad44104aa2..a749a5cb0a4e 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -839,13 +839,14 @@ struct nvmem_device *of_nvmem_device_get(struct device_node *np, const char *id) { struct device_node *nvmem_np; - int index; + int index = 0; - index = of_property_match_string(np, "nvmem-names", id); + if (id) + index = of_property_match_string(np, "nvmem-names", id); nvmem_np = of_parse_phandle(np, "nvmem", index); if (!nvmem_np) - return ERR_PTR(-EINVAL); + return ERR_PTR(-ENOENT); return __nvmem_device_get(nvmem_np, NULL); } -- cgit From 95b65195d571ab589a6d6893a418922757e06399 Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Mon, 28 Jan 2019 15:55:04 +0000 Subject: nvmem: core: Always reference the device returned by nvmem_device_get() In nvmem_device_get(), when the device lookup fails with DT it currently fallback on nvmem_find() which is wrong for two reasons. First nvmem_find() return NULL when nothing is found instead of an ERR_PTR. But nvmem_find() also just lookup the device, it doesn't reference the module and increment the reference count like it is done in the DT path. To fix this we replace the call to nvmem_find() with a call to __nvmem_device_get() which does all the referencing and return a proper ERR_PTR in case of error. Signed-off-by: Alban Bedel Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/nvmem') diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index a749a5cb0a4e..1752768dd2d2 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -874,7 +874,7 @@ struct nvmem_device *nvmem_device_get(struct device *dev, const char *dev_name) } - return nvmem_find(dev_name); + return __nvmem_device_get(NULL, dev_name); } EXPORT_SYMBOL_GPL(nvmem_device_get); -- cgit From 73e9dc4d568f050858e027655f0b0f6ddf65d6aa Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Mon, 28 Jan 2019 15:55:05 +0000 Subject: nvmem: core: Fix device reference leak __nvmem_device_get() make use of bus_find_device() to get the relevant device and this function increase the reference count of the device found, however this is not accounted for anywhere. Fix __nvmem_device_get() and __nvmem_device_put() to properly release this reference count. Signed-off-by: Alban Bedel Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/core.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/nvmem') diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 1752768dd2d2..5400017ef616 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -811,6 +811,7 @@ static struct nvmem_device *__nvmem_device_get(struct device_node *np, "could not increase module refcount for cell %s\n", nvmem_dev_name(nvmem)); + put_device(&nvmem->dev); return ERR_PTR(-EINVAL); } @@ -821,6 +822,7 @@ static struct nvmem_device *__nvmem_device_get(struct device_node *np, static void __nvmem_device_put(struct nvmem_device *nvmem) { + put_device(&nvmem->dev); module_put(nvmem->owner); kref_put(&nvmem->refcnt, nvmem_device_release); } -- cgit From 9bfd8198ba948e11a0f9c618db00b0d4020b71c5 Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Mon, 28 Jan 2019 15:55:06 +0000 Subject: nvmem: core: Avoid useless iterations in nvmem_cell_get_from_lookup() Once the correct cell has been found there is no need to continue iterating, just stop there. While at it replace the goto used to leave the loop with simple break statements. Signed-off-by: Alban Bedel Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/core.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'drivers/nvmem') diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 5400017ef616..9dd07eae1f3e 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -977,7 +977,7 @@ nvmem_cell_get_from_lookup(struct device *dev, const char *con_id) if (IS_ERR(nvmem)) { /* Provider may not be registered yet. */ cell = ERR_CAST(nvmem); - goto out; + break; } cell = nvmem_find_cell_by_name(nvmem, @@ -985,12 +985,11 @@ nvmem_cell_get_from_lookup(struct device *dev, const char *con_id) if (!cell) { __nvmem_device_put(nvmem); cell = ERR_PTR(-ENOENT); - goto out; } + break; } } -out: mutex_unlock(&nvmem_lookup_mutex); return cell; } -- cgit From 9f23379c67fa20b367ea53cfd989da5212316e6f Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sun, 3 Feb 2019 09:43:37 +0000 Subject: nvmem: allow to select i.MX nvmem driver for i.MX 7D The imx-ocotp nvmem driver supports the i.MX 7D SoC too. Allow to select the imx-ocotp driver even if only the i.MX 7D SoC has been selected. Fixes: 711d45477931 ("nvmem: octop: Add i.MX7D support") Signed-off-by: Stefan Agner Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/nvmem') diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 0a7a470ee859..9bbe2816400a 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -26,7 +26,7 @@ config NVMEM_IMX_IIM config NVMEM_IMX_OCOTP tristate "i.MX6 On-Chip OTP Controller support" - depends on SOC_IMX6 || COMPILE_TEST + depends on SOC_IMX6 || SOC_IMX7D || COMPILE_TEST depends on HAS_IOMEM help This is a driver for the On-Chip OTP Controller (OCOTP) available on -- cgit From f4853e1c321edb48af229ad5ac85076790d34968 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Fri, 15 Feb 2019 11:42:59 +0100 Subject: nvmem: core: don't check the return value of notifier chain call blocking_notifier_call_chain() returns the value returned by the last registered callback. A positive return value doesn't indicate an error and an nvmem device should correctly register irrespective of any notifier callback failures. Drop the retval check. Fixes: bee1138bea15 ("nvmem: add a notifier chain") Cc: stable@vger.kernel.org Signed-off-by: Bartosz Golaszewski Acked-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/core.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/nvmem') diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 9dd07eae1f3e..f24008b66826 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -688,9 +688,7 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) if (rval) goto err_remove_cells; - rval = blocking_notifier_call_chain(&nvmem_notifier, NVMEM_ADD, nvmem); - if (rval) - goto err_remove_cells; + blocking_notifier_call_chain(&nvmem_notifier, NVMEM_ADD, nvmem); return nvmem; -- cgit