/* * Copyright (C) 2015 Samsung Electronics * Przemyslaw Marczak * * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include #include #include #include #include DECLARE_GLOBAL_DATA_PTR; #define ADC_UCLASS_PLATDATA_SIZE sizeof(struct adc_uclass_platdata) #define CHECK_NUMBER true #define CHECK_MASK (!CHECK_NUMBER) /* TODO: add support for timer uclass (for early calls) */ #ifdef CONFIG_SANDBOX_ARCH #define sdelay(x) udelay(x) #else extern void sdelay(unsigned long loops); #endif static int check_channel(struct udevice *dev, int value, bool number_or_mask, const char *caller_function) { struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev); unsigned mask = number_or_mask ? (1 << value) : value; /* For the real ADC hardware, some ADC channels can be inactive. * For example if device has 4 analog channels, and only channels * 1-st and 3-rd are valid, then channel mask is: 0b1010, so request * with mask 0b1110 should return an error. */ if ((uc_pdata->channel_mask >= mask) && (uc_pdata->channel_mask & mask)) return 0; printf("Error in %s/%s().\nWrong channel selection for device: %s\n", __FILE__, caller_function, dev->name); return -EINVAL; } static int adc_supply_enable(struct udevice *dev) { struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev); const char *supply_type; int ret = 0; if (uc_pdata->vdd_supply) { supply_type = "vdd"; ret = regulator_set_enable(uc_pdata->vdd_supply, true); } if (!ret && uc_pdata->vss_supply) { supply_type = "vss"; ret = regulator_set_enable(uc_pdata->vss_supply, true); } if (ret) error("%s: can't enable %s-supply!", dev->name, supply_type); return ret; } int adc_data_mask(struct udevice *dev, unsigned int *data_mask) { struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev); if (!uc_pdata) return -ENOSYS; *data_mask = uc_pdata->data_mask; return 0; } int adc_stop(struct udevice *dev) { const struct adc_ops *ops = dev_get_driver_ops(dev); if (!ops->stop) return -ENOSYS; return ops->stop(dev); } int adc_start_channel(struct udevice *dev, int channel) { const struct adc_ops *ops = dev_get_driver_ops(dev); int ret; if (!ops->start_channel) return -ENOSYS; ret = check_channel(dev, channel, CHECK_NUMBER, __func__); if (ret) return ret; ret = adc_supply_enable(dev); if (ret) return ret; return ops->start_channel(dev, channel); } int adc_start_channels(struct udevice *dev, unsigned int channel_mask) { const struct adc_ops *ops = dev_get_driver_ops(dev); int ret; if (!ops->start_channels) return -ENOSYS; ret = check_channel(dev, channel_mask, CHECK_MASK, __func__); if (ret) return ret; ret = adc_supply_enable(dev); if (ret) return ret; return ops->start_channels(dev, channel_mask); } int adc_channel_data(struct udevice *dev, int channel, unsigned int *data) { struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev); const struct adc_ops *ops = dev_get_driver_ops(dev); unsigned int timeout_us = uc_pdata->data_timeout_us; int ret; if (!ops->channel_data) return -ENOSYS; ret = check_channel(dev, channel, CHECK_NUMBER, __func__); if (ret) return ret; do { ret = ops->channel_data(dev, channel, data); if (!ret || ret != -EBUSY) break; /* TODO: use timer uclass (for early calls). */ sdelay(5); } while (timeout_us--); return ret; } int adc_channels_data(struct udevice *dev, unsigned int channel_mask, struct adc_channel *channels) { struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev); unsigned int timeout_us = uc_pdata->multidata_timeout_us; const struct adc_ops *ops = dev_get_driver_ops(dev); int ret; if (!ops->channels_data) return -ENOSYS; ret = check_channel(dev, channel_mask, CHECK_MASK, __func__); if (ret) return ret; do { ret = ops->channels_data(dev, channel_mask, channels); if (!ret || ret != -EBUSY) break; /* TODO: use timer uclass (for early calls). */ sdelay(5); } while (timeout_us--); return ret; } int adc_channel_single_shot(const char *name, int channel, unsigned int *data) { struct udevice *dev; int ret; ret = uclass_get_device_by_name(UCLASS_ADC, name, &dev); if (ret) return ret; ret = adc_start_channel(dev, channel); if (ret) return ret; ret = adc_channel_data(dev, channel, data); if (ret) return ret; return 0; } static int _adc_channels_single_shot(struct udevice *dev, unsigned int channel_mask, struct adc_channel *channels) { unsigned int data; int channel, ret; for (channel = 0; channel <= ADC_MAX_CHANNEL; channel++) { /* Check channel bit. */ if (!((channel_mask >> channel) & 0x1)) continue; ret = adc_start_channel(dev, channel); if (ret) return ret; ret = adc_channel_data(dev, channel, &data); if (ret) return ret; channels->id = channel; channels->data = data; channels++; } return 0; } int adc_channels_single_shot(const char *name, unsigned int channel_mask, struct adc_channel *channels) { struct udevice *dev; int ret; ret = uclass_get_device_by_name(UCLASS_ADC, name, &dev); if (ret) return ret; ret = adc_start_channels(dev, channel_mask); if (ret) goto try_manual; ret = adc_channels_data(dev, channel_mask, channels); if (ret) return ret; return 0; try_manual: if (ret != -ENOSYS) return ret; return _adc_channels_single_shot(dev, channel_mask, channels); } static int adc_vdd_platdata_update(struct udevice *dev) { struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev); int ret; /* Warning! * This function can't return supply device before its bind. * Please pay attention to proper fdt scan sequence. If ADC device * will bind before its supply regulator device, then the below 'get' * will return an error. */ ret = device_get_supply_regulator(dev, "vdd-supply", &uc_pdata->vdd_supply); if (ret) return ret; ret = regulator_get_value(uc_pdata->vdd_supply); if (ret < 0) return ret; uc_pdata->vdd_microvolts = ret; return 0; } static int adc_vss_platdata_update(struct udevice *dev) { struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev); int ret; ret = device_get_supply_regulator(dev, "vss-supply", &uc_pdata->vss_supply); if (ret) return ret; ret = regulator_get_value(uc_pdata->vss_supply); if (ret < 0) return ret; uc_pdata->vss_microvolts = ret; return 0; } int adc_vdd_value(struct udevice *dev, int *uV) { struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev); int ret, value_sign = uc_pdata->vdd_polarity_negative ? -1 : 1; if (!uc_pdata->vdd_supply) goto nodev; /* Update the regulator Value. */ ret = adc_vdd_platdata_update(dev); if (ret) return ret; nodev: if (uc_pdata->vdd_microvolts == -ENODATA) return -ENODATA; *uV = uc_pdata->vdd_microvolts * value_sign; return 0; } int adc_vss_value(struct udevice *dev, int *uV) { struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev); int ret, value_sign = uc_pdata->vss_polarity_negative ? -1 : 1; if (!uc_pdata->vss_supply) goto nodev; /* Update the regulator Value. */ ret = adc_vss_platdata_update(dev); if (ret) return ret; nodev: if (uc_pdata->vss_microvolts == -ENODATA) return -ENODATA; *uV = uc_pdata->vss_microvolts * value_sign; return 0; } static int adc_vdd_platdata_set(struct udevice *dev) { struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev); int ret, offset = dev->of_offset; const void *fdt = gd->fdt_blob; char *prop; prop = "vdd-polarity-negative"; uc_pdata->vdd_polarity_negative = fdtdec_get_bool(fdt, offset, prop); ret = adc_vdd_platdata_update(dev); if (ret != -ENOENT) return ret; /* No vdd-supply phandle. */ prop = "vdd-microvolts"; uc_pdata->vdd_microvolts = fdtdec_get_int(fdt, offset, prop, -ENODATA); return 0; } static int adc_vss_platdata_set(struct udevice *dev) { struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev); int ret, offset = dev->of_offset; const void *fdt = gd->fdt_blob; char *prop; prop = "vss-polarity-negative"; uc_pdata->vss_polarity_negative = fdtdec_get_bool(fdt, offset, prop); ret = adc_vss_platdata_update(dev); if (ret != -ENOENT) return ret; /* No vss-supply phandle. */ prop = "vss-microvolts"; uc_pdata->vss_microvolts = fdtdec_get_int(fdt, offset, prop, -ENODATA); return 0; } static int adc_pre_probe(struct udevice *dev) { int ret; /* Set ADC VDD platdata: polarity, uV, regulator (phandle). */ ret = adc_vdd_platdata_set(dev); if (ret) error("%s: Can't update Vdd. Error: %d", dev->name, ret); /* Set ADC VSS platdata: polarity, uV, regulator (phandle). */ ret = adc_vss_platdata_set(dev); if (ret) error("%s: Can't update Vss. Error: %d", dev->name, ret); return 0; } UCLASS_DRIVER(adc) = { .id = UCLASS_ADC, .name = "adc", .pre_probe = adc_pre_probe, .per_device_platdata_auto_alloc_size = ADC_UCLASS_PLATDATA_SIZE, };