| /* |
| * Copyright (C) 2015 Samsung Electronics |
| * Przemyslaw Marczak <p.marczak@samsung.com> |
| * |
| * SPDX-License-Identifier: GPL-2.0+ |
| */ |
| |
| #include <common.h> |
| #include <errno.h> |
| #include <dm.h> |
| #include <dm/lists.h> |
| #include <dm/device-internal.h> |
| #include <dm/uclass-internal.h> |
| #include <adc.h> |
| #include <power/regulator.h> |
| |
| 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, |
| }; |