Message ID | 1427752670-17219-1-git-send-email-srinivas.kandagatla@linaro.org |
---|---|
State | New |
Headers | show |
Thanks Stephen for review, On 07/04/15 19:45, Stephen Boyd wrote: > On 03/30, Srinivas Kandagatla wrote: >> @@ -130,6 +138,37 @@ static struct class eeprom_class = { >> .dev_release = eeprom_release, >> }; >> >> +static int of_eeprom_match(struct device *dev, const void *eeprom_np) >> +{ >> + return dev->of_node == eeprom_np; >> +} >> + >> +static struct eeprom_device *of_eeprom_find(struct device_node *eeprom_np) >> +{ >> + struct device *d; >> + >> + if (!eeprom_np) >> + return NULL; >> + >> + d = class_find_device(&eeprom_class, NULL, eeprom_np, of_eeprom_match); >> + >> + return d ? to_eeprom(d) : NULL; >> +} >> + >> +static int eeprom_match(struct device *dev, const void *data) >> +{ >> + return !strcmp(dev_name(dev), (const char *)data); > > Is this cast necessary? May be an over do here, I will fix it. > >> +} >> + >> +static struct eeprom_device *eeprom_find(const char *name) >> +{ >> + struct device *d; >> + >> + d = class_find_device(&eeprom_class, NULL, (void *)name, eeprom_match); > > Is this cast necessary? I Will fix it in next version. > >> + >> + return d ? to_eeprom(d) : NULL; >> +} >> + >> /** >> * eeprom_register(): Register a eeprom device for given eeprom. >> * Also creates an binary entry in /sys/class/eeprom/name-id/eeprom >> + >> +/** >> + * eeprom_cell_get(): Get eeprom cell of device form a given eeprom name > > s/form/from/ Will fix this in next version. > >> + * and blocks. >> + * >> + * @ename: eeprom device name that needs to be looked-up. >> + * @blocks: eeprom blocks containing offset and length information. >> + * @nblocks: number of eeprom blocks. >> + * >> + * The return value will be an ERR_PTR() on error or a valid pointer >> + * to a struct eeprom_cell. The eeprom_cell will be freed by the >> + * eeprom_cell_put(). >> + */ >> +struct eeprom_cell *eeprom_cell_get(const char *ename, >> + struct eeprom_block *blocks, int nblocks) >> +{ >> + return __eeprom_cell_get(NULL, ename, blocks, nblocks); >> +} >> +EXPORT_SYMBOL_GPL(eeprom_cell_get); >> + >> +/** >> + * of_eeprom_cell_get(): Get eeprom cell of device form a given index > > s/form/from/ > Ok, >> + * >> + * @dev node: Device tree node that uses the eeprom cell >> + >> +#ifndef _LINUX_EEPROM_CONSUMER_H >> +#define _LINUX_EEPROM_CONSUMER_H >> + >> +struct eeprom_cell; >> + >> +struct eeprom_block { >> + loff_t offset; >> + size_t count; >> +}; >> +#if IS_ENABLED(CONFIG_EEPROM) >> +struct eeprom_cell *eeprom_cell_get(const char *ename, >> + struct eeprom_block *blocks, int nblocks); >> +void eeprom_cell_put(struct eeprom_cell *cell); >> +char *eeprom_cell_read(struct eeprom_cell *cell, ssize_t *len); >> +int eeprom_cell_write(struct eeprom_cell *cell, const char *buf, ssize_t len); > [...] >> + >> +#if IS_ENABLED(CONFIG_EEPROM) && IS_ENABLED(CONFIG_OF) >> +struct eeprom_cell *of_eeprom_cell_get(struct device_node *dev, >> + const char *property); >> +#else >> +static inline struct eeprom_cell *of_eeprom_cell_get(struct device_node *np, >> + const char *property) >> +{ >> + return ERR_PTR(-ENOSYS); >> +} >> +#endif >> +#endif /* ifndef _LINUX_EEPROM_CONSUMER_H */ > > Do you have an overview of how to use these APIs? Maybe some > Documentation/ is in order? I'm mostly interested in how the > blocks array is supposed to work and how this hooks up to drivers > that are using DT. Only doc ATM is function level kernel doc in c file. May be I can explain you for now and I will try to add some documentation with some usage examples in next version. eeprom block array is just another way intended to get hold of eeprom content for non-DT providers/consumers, but DT consumers/providers can also use it. As of today SOC/mach level code could use it as well. In eeprom_cell_get() case the lookup of provider is done based on provider name, this provider name is generally supplied by all the providers (both DT/non DT). for example in qfprom case, provider is registered from DT with eeprom config containing a unique name: static struct eeprom_config econfig = { .name = "qfprom", .id = 0, }; In the consumer case, the tsens driver could do some like in non DT way: struct eeprom_block blocks[4] ={ { .offset = 0x404, .count = 0x4, }, { .offset = 0x408, .count = 0x4, }, { .offset = 0x40c, .count = 0x4, }, { .offset = 0x410, .count = 0x4, }, }; calib_cell = eeprom_cell_get("qfprom0", blocks, 4); Or in DT way calib_cell = of_eeprom_cell_get(np, "calib"); --srini > -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi Stephen, Sorry I took so long to reply. On 09/04/15 15:45, Stephen Boyd wrote: > On 04/07, Srinivas Kandagatla wrote: >> On 07/04/15 19:45, Stephen Boyd wrote: >>> On 03/30, Srinivas Kandagatla wrote: >>> >>> Do you have an overview of how to use these APIs? Maybe some >>> Documentation/ is in order? I'm mostly interested in how the >>> blocks array is supposed to work and how this hooks up to drivers >>> that are using DT. >> >> Only doc ATM is function level kernel doc in c file. >> May be I can explain you for now and I will try to add some >> documentation with some usage examples in next version. > > Thanks. > >> >> eeprom block array is just another way intended to get hold of >> eeprom content for non-DT providers/consumers, but DT >> consumers/providers can also use it. As of today SOC/mach level code >> could use it as well. >> >> In eeprom_cell_get() case the lookup of provider is done based on >> provider name, this provider name is generally supplied by all the >> providers (both DT/non DT). >> >> for example in qfprom case, >> provider is registered from DT with eeprom config containing a unique name: >> static struct eeprom_config econfig = { >> .name = "qfprom", >> .id = 0, >> }; >> >> In the consumer case, the tsens driver could do some like in non DT way: >> >> struct eeprom_block blocks[4] ={ >> { >> .offset = 0x404, >> .count = 0x4, >> }, >> { >> .offset = 0x408, >> .count = 0x4, >> }, >> { >> .offset = 0x40c, >> .count = 0x4, >> }, >> { >> .offset = 0x410, >> .count = 0x4, >> }, >> }; >> calib_cell = eeprom_cell_get("qfprom0", blocks, 4); >> >> >> Or in DT way >> calib_cell = of_eeprom_cell_get(np, "calib"); >> > > Ok. I guess I was hoping for a more device centric approach like > we have for clks/regulators/etc. That way a driver doesn't need > to know it's using DT or not to figure out which API to call. That would be the best. Its easy to wrap up whats in eeprom core to eeprom_get_cell(dev, "cell-name") for both DT and non-dt cases, if I remove the nasty global name space thing. Only thing which is limiting it is the existing bindings which are just phandle based. For eeprom to be more of device centric we need more generic bindings/property names like nvrom-cell = <&abc>, <&edf> nvrom-cell-names = "cell1", "cell2"; Also we can have name associated to each eeprom cell which would help for non-dt cases. So they can just lookup by the cell name. Sacha, Are you ok with such binding? As this can provide a single interface for dt and non-dt. I remember you requested for changing from generic properties to specific property names. > Also the global namespace is sort of worrying (qfprom0 part). How > do we know it's qfprom0? What if it's qfprom1 sometimes? I agree this is something which I don't like it in the first place too. If we have something like names associated to each eeprom cell like clks or regulators we can have some thing like eeprom_get_cell(dev, name); > > Also, how are we supposed to encode certain bits inside the > stream of bytes (blocks)? In some situations (e.g. the qcom CPR > stuff) we have quite a few fuse bits we need to read that are > packed into different bytes and may cross byte boundaries (i.e. > bits 6 to 10 of a 32-bit word). The current API looks to be byte > focused when I have bit based/non-byte aligned data. Yes, it is more of byte oriented. However we can add some new apis for parsers like ones you are working on. of_eeprom_get_provider_regmap(phandle) just to get handle to regmap from eeprom_core, which would provide most of the apis you would need. Am guessing eeprom parsers would need need such interface to eeprom-core in future anyway. > > My current feeling is that I don't want to use any of the block > stuff at all. I just want a simple byte-based API that allows me > to read a number of contiguous bytes from the fuses. Then I'll > need to shift that data down by a few bits and mask it with the > width of the data I want to read to extract the data needed. > > The only thing after that where I have a problem is figuring out > which eeprom is present in the consumer driver. I think I may > need to open-code it and look at the phandle for the eeprom and > do a compatible check to figure out which bits I want to read. > > The DT would be like this (note I left eeprom-cells/eeproms here > because that allows us to generically find eeproms used by a > device and find eeprom providers): I though, having "eeprom-cells" would be make sense only if the bindings have possible arguments to a phandle. In this case it would be none all the time. For provider lookups currently its generic/easy and fast as its done based on eeprom class. Am not sure what advantage would we get Am I missing anything ? If you are ok I will prepare a v5 with the proposed changes. --srini > > qfprom: eeprom@58000 { > compatible = "qcom,qfprom-msm8916"; > reg = <0x58000 0x7000>; > #eeprom-cells = <0>; > }; > > cpr@b018000 { > compatible = "qcom,cpr"; > reg = <0xb018000 0x1000>; > interrupts = <0 15 1>, <0 16 1>, <0 17 1>; > eeproms = <&qfprom>; > ... > }; > -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/drivers/eeprom/core.c b/drivers/eeprom/core.c index a2c7e6c..7065275 100644 --- a/drivers/eeprom/core.c +++ b/drivers/eeprom/core.c @@ -16,6 +16,7 @@ #include <linux/device.h> #include <linux/eeprom-provider.h> +#include <linux/eeprom-consumer.h> #include <linux/export.h> #include <linux/fs.h> #include <linux/idr.h> @@ -38,6 +39,13 @@ struct eeprom_device { int users; }; +struct eeprom_cell { + struct eeprom_device *eeprom; + int nblocks; + int size; + struct eeprom_block blocks[0]; +}; + static DEFINE_MUTEX(eeprom_mutex); static DEFINE_IDA(eeprom_ida); @@ -130,6 +138,37 @@ static struct class eeprom_class = { .dev_release = eeprom_release, }; +static int of_eeprom_match(struct device *dev, const void *eeprom_np) +{ + return dev->of_node == eeprom_np; +} + +static struct eeprom_device *of_eeprom_find(struct device_node *eeprom_np) +{ + struct device *d; + + if (!eeprom_np) + return NULL; + + d = class_find_device(&eeprom_class, NULL, eeprom_np, of_eeprom_match); + + return d ? to_eeprom(d) : NULL; +} + +static int eeprom_match(struct device *dev, const void *data) +{ + return !strcmp(dev_name(dev), (const char *)data); +} + +static struct eeprom_device *eeprom_find(const char *name) +{ + struct device *d; + + d = class_find_device(&eeprom_class, NULL, (void *)name, eeprom_match); + + return d ? to_eeprom(d) : NULL; +} + /** * eeprom_register(): Register a eeprom device for given eeprom. * Also creates an binary entry in /sys/class/eeprom/name-id/eeprom @@ -214,6 +253,238 @@ int eeprom_unregister(struct eeprom_device *eeprom) } EXPORT_SYMBOL_GPL(eeprom_unregister); +static int eeprom_cell_sanity_check(struct eeprom_cell *cell) +{ + struct eeprom_device *eeprom = cell->eeprom; + int i; + + /* byte aligned, no need to check for stride sanity */ + if (eeprom->stride == 1) + return 0; + + for (i = 0; i < cell->nblocks; i++) { + if (!IS_ALIGNED(cell->blocks[i].offset, eeprom->stride)) { + dev_err(&eeprom->dev, + "cell unaligned to eeprom stride %d\n", + eeprom->stride); + return -EINVAL; + } + } + + return 0; +} + +static struct eeprom_cell *__eeprom_cell_get(struct device_node *cell_np, + const char *ename, + struct eeprom_block *blocks, + int nblocks) +{ + struct eeprom_cell *cell; + struct eeprom_device *eeprom; + struct property *prop; + const __be32 *vp; + u32 pv; + int i, rval; + + mutex_lock(&eeprom_mutex); + + eeprom = cell_np ? of_eeprom_find(cell_np->parent) : eeprom_find(ename); + if (!eeprom) { + mutex_unlock(&eeprom_mutex); + return ERR_PTR(-EPROBE_DEFER); + } + + eeprom->users++; + mutex_unlock(&eeprom_mutex); + + if (!try_module_get(eeprom->owner)) { + dev_err(&eeprom->dev, + "could not increase module refcount for cell %s\n", + ename); + rval = -EINVAL; + goto err_mod; + } + + if (cell_np) + nblocks = of_property_count_u32_elems(cell_np, "reg") / 2; + + cell = kzalloc(sizeof(*cell) + nblocks * sizeof(*blocks), GFP_KERNEL); + if (!cell) { + rval = -ENOMEM; + goto err_mem; + } + + cell->nblocks = nblocks; + cell->eeprom = eeprom; + cell->size = 0; + i = 0; + + if (cell_np) { + of_property_for_each_u32(cell_np, "reg", prop, vp, pv) { + cell->blocks[i].offset = pv; + vp = of_prop_next_u32(prop, vp, &pv); + cell->blocks[i].count = pv; + cell->size += pv; + i++; + } + } else { + memcpy(cell->blocks, blocks, nblocks * sizeof(*blocks)); + for (; i < nblocks; i++) + cell->size += blocks[i].count; + } + + if (IS_ERR_VALUE(eeprom_cell_sanity_check(cell))) { + rval = -EINVAL; + goto err_sanity; + } + + return cell; + +err_sanity: + kfree(cell); + +err_mem: + module_put(eeprom->owner); + +err_mod: + mutex_lock(&eeprom_mutex); + eeprom->users--; + mutex_unlock(&eeprom_mutex); + + return ERR_PTR(rval); + +} + +/** + * eeprom_cell_get(): Get eeprom cell of device form a given eeprom name + * and blocks. + * + * @ename: eeprom device name that needs to be looked-up. + * @blocks: eeprom blocks containing offset and length information. + * @nblocks: number of eeprom blocks. + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a struct eeprom_cell. The eeprom_cell will be freed by the + * eeprom_cell_put(). + */ +struct eeprom_cell *eeprom_cell_get(const char *ename, + struct eeprom_block *blocks, int nblocks) +{ + return __eeprom_cell_get(NULL, ename, blocks, nblocks); +} +EXPORT_SYMBOL_GPL(eeprom_cell_get); + +/** + * of_eeprom_cell_get(): Get eeprom cell of device form a given index + * + * @dev node: Device tree node that uses the eeprom cell + * @index: eeprom index in eeproms property. + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a struct eeprom_cell. The eeprom_cell will be freed by the + * eeprom_cell_put(). + */ +struct eeprom_cell *of_eeprom_cell_get(struct device_node *np, const char *name) +{ + struct device_node *cell_np; + + cell_np = of_parse_phandle(np, name, 0); + if (!cell_np) + return ERR_PTR(-EINVAL); + + return __eeprom_cell_get(cell_np, NULL, NULL, 0); +} +EXPORT_SYMBOL_GPL(of_eeprom_cell_get); + +/** + * eeprom_cell_put(): Release previously allocated eeprom cell. + * + * @cell: Previously allocated eeprom cell by eeprom_cell_get() + * or of_eeprom_cell_get(). + */ +void eeprom_cell_put(struct eeprom_cell *cell) +{ + struct eeprom_device *eeprom = cell->eeprom; + + mutex_lock(&eeprom_mutex); + eeprom->users--; + mutex_unlock(&eeprom_mutex); + module_put(eeprom->owner); + kfree(cell); +} +EXPORT_SYMBOL_GPL(eeprom_cell_put); + +/** + * eeprom_cell_read(): Read a given eeprom cell + * + * @cell: eeprom cell to be read. + * @len: pointer to length of cell which will be populated on successful read. + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a char * bufffer. The buffer should be freed by the consumer with a + * kfree(). + */ +char *eeprom_cell_read(struct eeprom_cell *cell, ssize_t *len) +{ + struct eeprom_device *eeprom = cell->eeprom; + char *buf; + int rc, i, offset = 0; + + if (!eeprom || !eeprom->regmap) + return ERR_PTR(-EINVAL); + + buf = kzalloc(cell->size, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < cell->nblocks; i++) { + rc = regmap_raw_read(eeprom->regmap, cell->blocks[i].offset, + buf + offset, cell->blocks[i].count); + + if (IS_ERR_VALUE(rc)) { + kfree(buf); + return ERR_PTR(rc); + } + offset += cell->blocks[i].count; + } + + *len = cell->size; + + return buf; +} +EXPORT_SYMBOL_GPL(eeprom_cell_read); + +/** + * eeprom_cell_write(): Write to a given eeprom cell + * + * @cell: eeprom cell to be written. + * @buf: Buffer to be written. + * @len: length of buffer to be written to eeprom cell. + * + * The return value will be an length of bytes written or non zero on failure. + */ +int eeprom_cell_write(struct eeprom_cell *cell, const char *buf, ssize_t len) +{ + struct eeprom_device *eeprom = cell->eeprom; + int i, rc, offset = 0; + + if (!eeprom || !eeprom->regmap || len != cell->size) + return -EINVAL; + + for (i = 0; i < cell->nblocks; i++) { + rc = regmap_raw_write(eeprom->regmap, cell->blocks[i].offset, + buf + offset, cell->blocks[i].count); + + if (IS_ERR_VALUE(rc)) + return rc; + + offset += cell->blocks[i].count; + } + + return len; +} +EXPORT_SYMBOL_GPL(eeprom_cell_write); + static int eeprom_init(void) { return class_register(&eeprom_class); diff --git a/include/linux/eeprom-consumer.h b/include/linux/eeprom-consumer.h new file mode 100644 index 0000000..effa417 --- /dev/null +++ b/include/linux/eeprom-consumer.h @@ -0,0 +1,61 @@ +/* + * EEPROM framework consumer. + * + * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + * Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef _LINUX_EEPROM_CONSUMER_H +#define _LINUX_EEPROM_CONSUMER_H + +struct eeprom_cell; + +struct eeprom_block { + loff_t offset; + size_t count; +}; +#if IS_ENABLED(CONFIG_EEPROM) +struct eeprom_cell *eeprom_cell_get(const char *ename, + struct eeprom_block *blocks, int nblocks); +void eeprom_cell_put(struct eeprom_cell *cell); +char *eeprom_cell_read(struct eeprom_cell *cell, ssize_t *len); +int eeprom_cell_write(struct eeprom_cell *cell, const char *buf, ssize_t len); +#else + +static inline struct eeprom_cell *eeprom_cell_get(const char *ename, + struct eeprom_block *blocks, int nblocks) +{ + return ERR_PTR(-ENOSYS); +} + +static inline void eeprom_cell_put(struct eeprom_cell *cell) +{ +} + +static inline char *eeprom_cell_read(struct eeprom_cell *cell, ssize_t *len) +{ + return ERR_PTR(-ENOSYS); +} + +static inline int eeprom_cell_write(struct eeprom_cell *cell, + const char *buf, ssize_t len) +{ + return -ENOSYS; +} +#endif /* CONFIG_EEPROM */ + +#if IS_ENABLED(CONFIG_EEPROM) && IS_ENABLED(CONFIG_OF) +struct eeprom_cell *of_eeprom_cell_get(struct device_node *dev, + const char *property); +#else +static inline struct eeprom_cell *of_eeprom_cell_get(struct device_node *np, + const char *property) +{ + return ERR_PTR(-ENOSYS); +} +#endif +#endif /* ifndef _LINUX_EEPROM_CONSUMER_H */