Changes in 4.9.276 ALSA: usb-audio: fix rate on Ozone Z90 USB headset media: dvb-usb: fix wrong definition Input: usbtouchscreen - fix control-request directions net: can: ems_usb: fix use-after-free in ems_usb_disconnect() usb: gadget: eem: fix echo command packet response issue USB: cdc-acm: blacklist Heimann USB Appset device ntfs: fix validity check for file name attribute iov_iter_fault_in_readable() should do nothing in xarray case Input: joydev - prevent use of not validated data in JSIOCSBTNMAP ioctl ARM: dts: at91: sama5d4: fix pinctrl muxing btrfs: clear defrag status of a root if starting transaction fails ext4: fix kernel infoleak via ext4_extent_header ext4: correct the cache_nr in tracepoint ext4_es_shrink_exit ext4: remove check for zero nr_to_scan in ext4_es_scan() ext4: fix avefreec in find_group_orlov SUNRPC: Fix the batch tasks count wraparound. SUNRPC: Should wake up the privileged task firstly. s390/cio: dont call css_wait_for_slow_path() inside a lock iio: ltr501: mark register holding upper 8 bits of ALS_DATA{0,1} and PS_DATA as volatile, too iio: ltr501: ltr559: fix initialization of LTR501_ALS_CONTR iio: ltr501: ltr501_read_ps(): add missing endianness conversion serial: sh-sci: Stop dmaengine transfer in sci_stop_tx() serial_cs: Add Option International GSM-Ready 56K/ISDN modem serial_cs: remove wrong GLOBETROTTER.cis entry ath9k: Fix kernel NULL pointer dereference during ath_reset_internal() ssb: sdio: Don't overwrite const buffer if block_write fails seq_buf: Make trace_seq_putmem_hex() support data longer than 8 fuse: check connected before queueing on fpq->io spi: spi-loopback-test: Fix 'tx_buf' might be 'rx_buf' spi: spi-topcliff-pch: Fix potential double free in pch_spi_process_messages() spi: omap-100k: Fix the length judgment problem crypto: nx - add missing MODULE_DEVICE_TABLE media: cpia2: fix memory leak in cpia2_usb_probe media: cobalt: fix race condition in setting HPD media: pvrusb2: fix warning in pvr2_i2c_core_done crypto: qat - check return code of qat_hal_rd_rel_reg() crypto: qat - remove unused macro in FW loader media: v4l2-core: Avoid the dangling pointer in v4l2_fh_release media: bt8xx: Fix a missing check bug in bt878_probe media: st-hva: Fix potential NULL pointer dereferences mmc: via-sdmmc: add a check against NULL pointer dereference crypto: shash - avoid comparing pointers to exported functions under CFI media: dvb_net: avoid speculation from net slot media: siano: fix device register error path btrfs: abort transaction if we fail to update the delayed inode btrfs: disable build on platforms having page size 256K regulator: da9052: Ensure enough delay time for .set_voltage_time_sel ACPI: processor idle: Fix up C-state latency if not ordered block_dump: remove block_dump feature in mark_inode_dirty() fs: dlm: cancel work sync othercon random32: Fix implicit truncation warning in prandom_seed_state() fs: dlm: fix memory leak when fenced ACPI: bus: Call kobject_put() in acpi_init() error path platform/x86: toshiba_acpi: Fix missing error code in toshiba_acpi_setup_keyboard() ACPI: tables: Add custom DSDT file as makefile prerequisite ia64: mca_drv: fix incorrect array size calculation media: s5p_cec: decrement usage count if disabled crypto: ixp4xx - dma_unmap the correct address crypto: ux500 - Fix error return code in hash_hw_final() sata_highbank: fix deferred probing pata_rb532_cf: fix deferred probing media: I2C: change 'RST' to "RSET" to fix multiple build errors pata_octeon_cf: avoid WARN_ON() in ata_host_activate() pata_ep93xx: fix deferred probing media: tc358743: Fix error return code in tc358743_probe_of() media: siano: Fix out-of-bounds warnings in smscore_load_firmware_family2() mmc: usdhi6rol0: fix error return code in usdhi6_probe() media: s5p-g2d: Fix a memory leak on ctx->fh.m2m_ctx hwmon: (max31722) Remove non-standard ACPI device IDs hwmon: (max31790) Fix fan speed reporting for fan7..12 spi: spi-sun6i: Fix chipselect/clock bug crypto: nx - Fix RCU warning in nx842_OF_upd_status ACPI: sysfs: Fix a buffer overrun problem with description_show() ocfs2: fix snprintf() checking net: pch_gbe: Propagate error from devm_gpio_request_one() ehea: fix error return code in ehea_restart_qps() RDMA/rxe: Fix failure during driver load drm: qxl: ensure surf.data is ininitialized wireless: carl9170: fix LEDS build errors & warnings brcmsmac: mac80211_if: Fix a resource leak in an error handling path ath10k: Fix an error code in ath10k_add_interface() netlabel: Fix memory leak in netlbl_mgmt_add_common netfilter: nft_exthdr: check for IPv6 packet before further processing net: ethernet: aeroflex: fix UAF in greth_of_remove net: ethernet: ezchip: fix UAF in nps_enet_remove net: ethernet: ezchip: fix error handling vxlan: add missing rcu_read_lock() in neigh_reduce() i40e: Fix error handling in i40e_vsi_open Bluetooth: mgmt: Fix slab-out-of-bounds in tlv_data_is_valid writeback: fix obtain a reference to a freeing memcg css net: sched: fix warning in tcindex_alloc_perfect_hash tty: nozomi: Fix a resource leak in an error handling function iio: adis_buffer: do not return ints in irq handlers iio: accel: bma180: Fix buffer alignment in iio_push_to_buffers_with_timestamp() iio: accel: bma220: Fix buffer alignment in iio_push_to_buffers_with_timestamp() iio: accel: kxcjk-1013: Fix buffer alignment in iio_push_to_buffers_with_timestamp() iio: accel: stk8312: Fix buffer alignment in iio_push_to_buffers_with_timestamp() iio: accel: stk8ba50: Fix buffer alignment in iio_push_to_buffers_with_timestamp() iio: adc: ti-ads1015: Fix buffer alignment in iio_push_to_buffers_with_timestamp() iio: adc: vf610: Fix buffer alignment in iio_push_to_buffers_with_timestamp() iio: gyro: bmg160: Fix buffer alignment in iio_push_to_buffers_with_timestamp() iio: humidity: am2315: Fix buffer alignment in iio_push_to_buffers_with_timestamp() iio: prox: pulsed-light: Fix buffer alignment in iio_push_to_buffers_with_timestamp() iio: light: isl29125: Fix buffer alignment in iio_push_to_buffers_with_timestamp() iio: light: tcs3414: Fix buffer alignment in iio_push_to_buffers_with_timestamp() Input: hil_kbd - fix error return code in hil_dev_connect() char: pcmcia: error out if 'num_bytes_read' is greater than 4 in set_protocol() tty: nozomi: Fix the error handling path of 'nozomi_card_init()' scsi: FlashPoint: Rename si_flags field s390: appldata depends on PROC_SYSCTL staging: gdm724x: check for buffer overflow in gdm_lte_multi_sdu_pkt() staging: gdm724x: check for overflow in gdm_lte_netif_rx() of: Fix truncation of memory sizes on 32-bit platforms scsi: mpt3sas: Fix error return value in _scsih_expander_add() phy: ti: dm816x: Fix the error handling path in 'dm816x_usb_phy_probe() extcon: sm5502: Drop invalid register write in sm5502_reg_data extcon: max8997: Add missing modalias string configfs: fix memleak in configfs_release_bin_file leds: ktd2692: Fix an error handling path mm/huge_memory.c: don't discard hugepage if other processes are mapping it selftests/vm/pkeys: fix alloc_random_pkey() to make it really, really random mmc: vub3000: fix control-request direction scsi: core: Retry I/O for Notify (Enable Spinup) Required error net: pch_gbe: Use proper accessors to BE data in pch_ptp_match() hugetlb: clear huge pte during flush function on mips platform atm: iphase: fix possible use-after-free in ia_module_exit() mISDN: fix possible use-after-free in HFC_cleanup() atm: nicstar: Fix possible use-after-free in nicstar_cleanup() net: Treat __napi_schedule_irqoff() as __napi_schedule() on PREEMPT_RT reiserfs: add check for invalid 1st journal block drm/virtio: Fix double free on probe failure udf: Fix NULL pointer dereference in udf_symlink function e100: handle eeprom as little endian clk: tegra: Ensure that PLLU configuration is applied properly ipv6: use prandom_u32() for ID generation RDMA/cxgb4: Fix missing error code in create_qp() dm space maps: don't reset space map allocation cursor when committing net: micrel: check return value after calling platform_get_resource() fjes: check return value after calling platform_get_resource() selinux: use __GFP_NOWARN with GFP_NOWAIT in the AVC xfrm: Fix error reporting in xfrm_state_construct. wlcore/wl12xx: Fix wl12xx get_mac error if device is in ELP wl1251: Fix possible buffer overflow in wl1251_cmd_scan cw1200: add missing MODULE_DEVICE_TABLE MIPS: add PMD table accounting into MIPS'pmd_alloc_one atm: nicstar: use 'dma_free_coherent' instead of 'kfree' atm: nicstar: register the interrupt handler in the right place RDMA/rxe: Don't overwrite errno from ib_umem_get() sfc: avoid double pci_remove of VFs sfc: error code if SRIOV cannot be disabled wireless: wext-spy: Fix out-of-bounds warning RDMA/cma: Fix rdma_resolve_route() memory leak Bluetooth: Fix the HCI to MGMT status conversion table Bluetooth: Shutdown controller after workqueues are flushed or cancelled Bluetooth: btusb: fix bt fiwmare downloading failure issue for qca btsoc. sctp: add size validation when walking chunks fuse: reject internal errno can: gw: synchronize rcu operations before removing gw job entry can: bcm: delay release of struct bcm_op after synchronize_rcu() mac80211: fix memory corruption in EAPOL handling powerpc/barrier: Avoid collision with clang's __lwsync macro pinctrl/amd: Add device HID for new AMD GPIO controller mmc: sdhci: Fix warning message when accessing RPMB in HS400 mode mmc: core: clear flags before allowing to retune ata: ahci_sunxi: Disable DIPM ASoC: tegra: Set driver_name=tegra for all machine drivers qemu_fw_cfg: Make fw_cfg_rev_attr a proper kobj_attribute ipmi/watchdog: Stop watchdog timer when the current action is 'none' power: supply: ab8500: Fix an old bug seq_buf: Fix overflow in seq_buf_putmem_hex() ipack/carriers/tpci200: Fix a double free in tpci200_pci_probe dm btree remove: assign new_root only when removal succeeds media: dtv5100: fix control-request directions media: zr364xx: fix memory leak in zr364xx_start_readpipe media: gspca/sq905: fix control-request direction media: gspca/sunplus: fix zero-length control requests media: uvcvideo: Fix pixel format change for Elgato Cam Link 4K jfs: fix GPF in diFree smackfs: restrict bytes count in smk_set_cipso() KVM: x86: Use guest MAXPHYADDR from CPUID.0x8000_0008 iff TDP is enabled KVM: X86: Disable hardware breakpoints unconditionally before kvm_x86->run() scsi: core: Fix bad pointer dereference when ehandler kthread is invalid tracing: Do not reference char * as a string in histograms fscrypt: don't ignore minor_hash when hash is 0 tty: serial: fsl_lpuart: fix the potential risk of division or modulo by zero misc/libmasm/module: Fix two use after free in ibmasm_init_one Revert "ALSA: bebob/oxfw: fix Kconfig entry for Mackie d.2 Pro" scsi: lpfc: Fix "Unexpected timeout" error in direct attach topology tty: serial: 8250: serial_cs: Fix a memory leak in error handling path fs/jfs: Fix missing error code in lmLogInit() scsi: iscsi: Add iscsi_cls_conn refcount helpers mfd: da9052/stmpe: Add and modify MODULE_DEVICE_TABLE s390/sclp_vt220: fix console name to match device ALSA: sb: Fix potential double-free of CSP mixer elements powerpc/ps3: Add dma_mask to ps3_dma_region gpio: zynq: Check return value of pm_runtime_get_sync ALSA: ppc: fix error return code in snd_pmac_probe() selftests/powerpc: Fix "no_handler" EBB selftest ASoC: soc-core: Fix the error return code in snd_soc_of_parse_audio_routing() ALSA: bebob: add support for ToneWeal FW66 usb: gadget: f_hid: fix endianness issue with descriptors usb: gadget: hid: fix error return code in hid_bind() powerpc/boot: Fixup device-tree on little endian backlight: lm3630a: Fix return code of .update_status() callback ALSA: hda: Add IRQ check for platform_get_irq() i2c: core: Disable client irq on reboot/shutdown lib/decompress_unlz4.c: correctly handle zero-padding around initrds. pwm: spear: Don't modify HW state in .remove callback power: supply: ab8500: Avoid NULL pointers power: reset: gpio-poweroff: add missing MODULE_DEVICE_TABLE ARM: 9087/1: kprobes: test-thumb: fix for LLVM_IAS=1 watchdog: Fix possible use-after-free in wdt_startup() watchdog: sc520_wdt: Fix possible use-after-free in wdt_turnoff() watchdog: Fix possible use-after-free by calling del_timer_sync() x86/fpu: Return proper error codes from user access functions orangefs: fix orangefs df output. ceph: remove bogus checks and WARN_ONs from ceph_set_page_dirty power: supply: charger-manager: add missing MODULE_DEVICE_TABLE power: supply: ab8500: add missing MODULE_DEVICE_TABLE pwm: tegra: Don't modify HW state in .remove callback ACPI: AMBA: Fix resource name in /proc/iomem virtio-blk: Fix memory leak among suspend/resume procedure virtio_console: Assure used length from device is limited PCI/sysfs: Fix dsm_label_utf16s_to_utf8s() buffer overrun power: supply: rt5033_battery: Fix device tree enumeration um: fix error return code in slip_open() um: fix error return code in winch_tramp() watchdog: aspeed: fix hardware timeout calculation nfs: fix acl memory leak of posix_acl_create() ubifs: Set/Clear I_LINKABLE under i_lock for whiteout inode x86/fpu: Limit xstate copy size in xstateregs_set() ALSA: isa: Fix error return code in snd_cmi8330_probe() hexagon: use common DISCARDS macro ARM: dts: exynos: fix PWM LED max brightness on Odroid XU/XU3 ARM: dts: exynos: fix PWM LED max brightness on Odroid XU4 rtc: fix snprintf() checking in is_rtc_hctosys() ARM: dts: r8a7779, marzen: Fix DU clock names reset: bail if try_module_get() fails memory: fsl_ifc: fix leak of IO mapping on probe failure memory: fsl_ifc: fix leak of private memory on probe failure ARM: dts: am335x: align ti,pindir-d0-out-d1-in property with dt-shema scsi: be2iscsi: Fix an error handling path in beiscsi_dev_probe() mips: always link byteswap helpers into decompressor mips: disable branch profiling in boot/decompress.o MIPS: vdso: Invalid GIC access through VDSO seq_file: disallow extremely large seq buffer allocations Linux 4.9.276 Signed-off-by: Greg Kroah-Hartman <gregkh@google.com> Change-Id: I595c090068eb1b1934b15a0d54394abc38b4b0cc
999 lines
24 KiB
C
999 lines
24 KiB
C
/*
|
|
* Freescale Vybrid vf610 ADC driver
|
|
*
|
|
* Copyright 2013 Freescale Semiconductor, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/io.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/err.h>
|
|
|
|
#include <linux/iio/iio.h>
|
|
#include <linux/iio/buffer.h>
|
|
#include <linux/iio/sysfs.h>
|
|
#include <linux/iio/trigger.h>
|
|
#include <linux/iio/trigger_consumer.h>
|
|
#include <linux/iio/triggered_buffer.h>
|
|
|
|
/* This will be the driver name the kernel reports */
|
|
#define DRIVER_NAME "vf610-adc"
|
|
|
|
/* Vybrid/IMX ADC registers */
|
|
#define VF610_REG_ADC_HC0 0x00
|
|
#define VF610_REG_ADC_HC1 0x04
|
|
#define VF610_REG_ADC_HS 0x08
|
|
#define VF610_REG_ADC_R0 0x0c
|
|
#define VF610_REG_ADC_R1 0x10
|
|
#define VF610_REG_ADC_CFG 0x14
|
|
#define VF610_REG_ADC_GC 0x18
|
|
#define VF610_REG_ADC_GS 0x1c
|
|
#define VF610_REG_ADC_CV 0x20
|
|
#define VF610_REG_ADC_OFS 0x24
|
|
#define VF610_REG_ADC_CAL 0x28
|
|
#define VF610_REG_ADC_PCTL 0x30
|
|
|
|
/* Configuration register field define */
|
|
#define VF610_ADC_MODE_BIT8 0x00
|
|
#define VF610_ADC_MODE_BIT10 0x04
|
|
#define VF610_ADC_MODE_BIT12 0x08
|
|
#define VF610_ADC_MODE_MASK 0x0c
|
|
#define VF610_ADC_BUSCLK2_SEL 0x01
|
|
#define VF610_ADC_ALTCLK_SEL 0x02
|
|
#define VF610_ADC_ADACK_SEL 0x03
|
|
#define VF610_ADC_ADCCLK_MASK 0x03
|
|
#define VF610_ADC_CLK_DIV2 0x20
|
|
#define VF610_ADC_CLK_DIV4 0x40
|
|
#define VF610_ADC_CLK_DIV8 0x60
|
|
#define VF610_ADC_CLK_MASK 0x60
|
|
#define VF610_ADC_ADLSMP_LONG 0x10
|
|
#define VF610_ADC_ADSTS_SHORT 0x100
|
|
#define VF610_ADC_ADSTS_NORMAL 0x200
|
|
#define VF610_ADC_ADSTS_LONG 0x300
|
|
#define VF610_ADC_ADSTS_MASK 0x300
|
|
#define VF610_ADC_ADLPC_EN 0x80
|
|
#define VF610_ADC_ADHSC_EN 0x400
|
|
#define VF610_ADC_REFSEL_VALT 0x800
|
|
#define VF610_ADC_REFSEL_VBG 0x1000
|
|
#define VF610_ADC_ADTRG_HARD 0x2000
|
|
#define VF610_ADC_AVGS_8 0x4000
|
|
#define VF610_ADC_AVGS_16 0x8000
|
|
#define VF610_ADC_AVGS_32 0xC000
|
|
#define VF610_ADC_AVGS_MASK 0xC000
|
|
#define VF610_ADC_OVWREN 0x10000
|
|
|
|
/* General control register field define */
|
|
#define VF610_ADC_ADACKEN 0x1
|
|
#define VF610_ADC_DMAEN 0x2
|
|
#define VF610_ADC_ACREN 0x4
|
|
#define VF610_ADC_ACFGT 0x8
|
|
#define VF610_ADC_ACFE 0x10
|
|
#define VF610_ADC_AVGEN 0x20
|
|
#define VF610_ADC_ADCON 0x40
|
|
#define VF610_ADC_CAL 0x80
|
|
|
|
/* Other field define */
|
|
#define VF610_ADC_ADCHC(x) ((x) & 0x1F)
|
|
#define VF610_ADC_AIEN (0x1 << 7)
|
|
#define VF610_ADC_CONV_DISABLE 0x1F
|
|
#define VF610_ADC_HS_COCO0 0x1
|
|
#define VF610_ADC_CALF 0x2
|
|
#define VF610_ADC_TIMEOUT msecs_to_jiffies(100)
|
|
|
|
#define DEFAULT_SAMPLE_TIME 1000
|
|
|
|
/* V at 25°C of 696 mV */
|
|
#define VF610_VTEMP25_3V0 950
|
|
/* V at 25°C of 699 mV */
|
|
#define VF610_VTEMP25_3V3 867
|
|
/* Typical sensor slope coefficient at all temperatures */
|
|
#define VF610_TEMP_SLOPE_COEFF 1840
|
|
|
|
enum clk_sel {
|
|
VF610_ADCIOC_BUSCLK_SET,
|
|
VF610_ADCIOC_ALTCLK_SET,
|
|
VF610_ADCIOC_ADACK_SET,
|
|
};
|
|
|
|
enum vol_ref {
|
|
VF610_ADCIOC_VR_VREF_SET,
|
|
VF610_ADCIOC_VR_VALT_SET,
|
|
VF610_ADCIOC_VR_VBG_SET,
|
|
};
|
|
|
|
enum average_sel {
|
|
VF610_ADC_SAMPLE_1,
|
|
VF610_ADC_SAMPLE_4,
|
|
VF610_ADC_SAMPLE_8,
|
|
VF610_ADC_SAMPLE_16,
|
|
VF610_ADC_SAMPLE_32,
|
|
};
|
|
|
|
enum conversion_mode_sel {
|
|
VF610_ADC_CONV_NORMAL,
|
|
VF610_ADC_CONV_HIGH_SPEED,
|
|
VF610_ADC_CONV_LOW_POWER,
|
|
};
|
|
|
|
enum lst_adder_sel {
|
|
VF610_ADCK_CYCLES_3,
|
|
VF610_ADCK_CYCLES_5,
|
|
VF610_ADCK_CYCLES_7,
|
|
VF610_ADCK_CYCLES_9,
|
|
VF610_ADCK_CYCLES_13,
|
|
VF610_ADCK_CYCLES_17,
|
|
VF610_ADCK_CYCLES_21,
|
|
VF610_ADCK_CYCLES_25,
|
|
};
|
|
|
|
struct vf610_adc_feature {
|
|
enum clk_sel clk_sel;
|
|
enum vol_ref vol_ref;
|
|
enum conversion_mode_sel conv_mode;
|
|
|
|
int clk_div;
|
|
int sample_rate;
|
|
int res_mode;
|
|
u32 lst_adder_index;
|
|
u32 default_sample_time;
|
|
|
|
bool calibration;
|
|
bool ovwren;
|
|
};
|
|
|
|
struct vf610_adc {
|
|
struct device *dev;
|
|
void __iomem *regs;
|
|
struct clk *clk;
|
|
|
|
u32 vref_uv;
|
|
u32 value;
|
|
struct regulator *vref;
|
|
|
|
u32 max_adck_rate[3];
|
|
struct vf610_adc_feature adc_feature;
|
|
|
|
u32 sample_freq_avail[5];
|
|
|
|
struct completion completion;
|
|
/* Ensure the timestamp is naturally aligned */
|
|
struct {
|
|
u16 chan;
|
|
s64 timestamp __aligned(8);
|
|
} scan;
|
|
};
|
|
|
|
static const u32 vf610_hw_avgs[] = { 1, 4, 8, 16, 32 };
|
|
static const u32 vf610_lst_adder[] = { 3, 5, 7, 9, 13, 17, 21, 25 };
|
|
|
|
static inline void vf610_adc_calculate_rates(struct vf610_adc *info)
|
|
{
|
|
struct vf610_adc_feature *adc_feature = &info->adc_feature;
|
|
unsigned long adck_rate, ipg_rate = clk_get_rate(info->clk);
|
|
u32 adck_period, lst_addr_min;
|
|
int divisor, i;
|
|
|
|
adck_rate = info->max_adck_rate[adc_feature->conv_mode];
|
|
|
|
if (adck_rate) {
|
|
/* calculate clk divider which is within specification */
|
|
divisor = ipg_rate / adck_rate;
|
|
adc_feature->clk_div = 1 << fls(divisor + 1);
|
|
} else {
|
|
/* fall-back value using a safe divisor */
|
|
adc_feature->clk_div = 8;
|
|
}
|
|
|
|
adck_rate = ipg_rate / adc_feature->clk_div;
|
|
|
|
/*
|
|
* Determine the long sample time adder value to be used based
|
|
* on the default minimum sample time provided.
|
|
*/
|
|
adck_period = NSEC_PER_SEC / adck_rate;
|
|
lst_addr_min = adc_feature->default_sample_time / adck_period;
|
|
for (i = 0; i < ARRAY_SIZE(vf610_lst_adder); i++) {
|
|
if (vf610_lst_adder[i] > lst_addr_min) {
|
|
adc_feature->lst_adder_index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Calculate ADC sample frequencies
|
|
* Sample time unit is ADCK cycles. ADCK clk source is ipg clock,
|
|
* which is the same as bus clock.
|
|
*
|
|
* ADC conversion time = SFCAdder + AverageNum x (BCT + LSTAdder)
|
|
* SFCAdder: fixed to 6 ADCK cycles
|
|
* AverageNum: 1, 4, 8, 16, 32 samples for hardware average.
|
|
* BCT (Base Conversion Time): fixed to 25 ADCK cycles for 12 bit mode
|
|
* LSTAdder(Long Sample Time): 3, 5, 7, 9, 13, 17, 21, 25 ADCK cycles
|
|
*/
|
|
for (i = 0; i < ARRAY_SIZE(vf610_hw_avgs); i++)
|
|
info->sample_freq_avail[i] =
|
|
adck_rate / (6 + vf610_hw_avgs[i] *
|
|
(25 + vf610_lst_adder[adc_feature->lst_adder_index]));
|
|
}
|
|
|
|
static inline void vf610_adc_cfg_init(struct vf610_adc *info)
|
|
{
|
|
struct vf610_adc_feature *adc_feature = &info->adc_feature;
|
|
|
|
/* set default Configuration for ADC controller */
|
|
adc_feature->clk_sel = VF610_ADCIOC_BUSCLK_SET;
|
|
adc_feature->vol_ref = VF610_ADCIOC_VR_VREF_SET;
|
|
|
|
adc_feature->calibration = true;
|
|
adc_feature->ovwren = true;
|
|
|
|
adc_feature->res_mode = 12;
|
|
adc_feature->sample_rate = 1;
|
|
|
|
adc_feature->conv_mode = VF610_ADC_CONV_LOW_POWER;
|
|
|
|
vf610_adc_calculate_rates(info);
|
|
}
|
|
|
|
static void vf610_adc_cfg_post_set(struct vf610_adc *info)
|
|
{
|
|
struct vf610_adc_feature *adc_feature = &info->adc_feature;
|
|
int cfg_data = 0;
|
|
int gc_data = 0;
|
|
|
|
switch (adc_feature->clk_sel) {
|
|
case VF610_ADCIOC_ALTCLK_SET:
|
|
cfg_data |= VF610_ADC_ALTCLK_SEL;
|
|
break;
|
|
case VF610_ADCIOC_ADACK_SET:
|
|
cfg_data |= VF610_ADC_ADACK_SEL;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* low power set for calibration */
|
|
cfg_data |= VF610_ADC_ADLPC_EN;
|
|
|
|
/* enable high speed for calibration */
|
|
cfg_data |= VF610_ADC_ADHSC_EN;
|
|
|
|
/* voltage reference */
|
|
switch (adc_feature->vol_ref) {
|
|
case VF610_ADCIOC_VR_VREF_SET:
|
|
break;
|
|
case VF610_ADCIOC_VR_VALT_SET:
|
|
cfg_data |= VF610_ADC_REFSEL_VALT;
|
|
break;
|
|
case VF610_ADCIOC_VR_VBG_SET:
|
|
cfg_data |= VF610_ADC_REFSEL_VBG;
|
|
break;
|
|
default:
|
|
dev_err(info->dev, "error voltage reference\n");
|
|
}
|
|
|
|
/* data overwrite enable */
|
|
if (adc_feature->ovwren)
|
|
cfg_data |= VF610_ADC_OVWREN;
|
|
|
|
writel(cfg_data, info->regs + VF610_REG_ADC_CFG);
|
|
writel(gc_data, info->regs + VF610_REG_ADC_GC);
|
|
}
|
|
|
|
static void vf610_adc_calibration(struct vf610_adc *info)
|
|
{
|
|
int adc_gc, hc_cfg;
|
|
|
|
if (!info->adc_feature.calibration)
|
|
return;
|
|
|
|
/* enable calibration interrupt */
|
|
hc_cfg = VF610_ADC_AIEN | VF610_ADC_CONV_DISABLE;
|
|
writel(hc_cfg, info->regs + VF610_REG_ADC_HC0);
|
|
|
|
adc_gc = readl(info->regs + VF610_REG_ADC_GC);
|
|
writel(adc_gc | VF610_ADC_CAL, info->regs + VF610_REG_ADC_GC);
|
|
|
|
if (!wait_for_completion_timeout(&info->completion, VF610_ADC_TIMEOUT))
|
|
dev_err(info->dev, "Timeout for adc calibration\n");
|
|
|
|
adc_gc = readl(info->regs + VF610_REG_ADC_GS);
|
|
if (adc_gc & VF610_ADC_CALF)
|
|
dev_err(info->dev, "ADC calibration failed\n");
|
|
|
|
info->adc_feature.calibration = false;
|
|
}
|
|
|
|
static void vf610_adc_cfg_set(struct vf610_adc *info)
|
|
{
|
|
struct vf610_adc_feature *adc_feature = &(info->adc_feature);
|
|
int cfg_data;
|
|
|
|
cfg_data = readl(info->regs + VF610_REG_ADC_CFG);
|
|
|
|
cfg_data &= ~VF610_ADC_ADLPC_EN;
|
|
if (adc_feature->conv_mode == VF610_ADC_CONV_LOW_POWER)
|
|
cfg_data |= VF610_ADC_ADLPC_EN;
|
|
|
|
cfg_data &= ~VF610_ADC_ADHSC_EN;
|
|
if (adc_feature->conv_mode == VF610_ADC_CONV_HIGH_SPEED)
|
|
cfg_data |= VF610_ADC_ADHSC_EN;
|
|
|
|
writel(cfg_data, info->regs + VF610_REG_ADC_CFG);
|
|
}
|
|
|
|
static void vf610_adc_sample_set(struct vf610_adc *info)
|
|
{
|
|
struct vf610_adc_feature *adc_feature = &(info->adc_feature);
|
|
int cfg_data, gc_data;
|
|
|
|
cfg_data = readl(info->regs + VF610_REG_ADC_CFG);
|
|
gc_data = readl(info->regs + VF610_REG_ADC_GC);
|
|
|
|
/* resolution mode */
|
|
cfg_data &= ~VF610_ADC_MODE_MASK;
|
|
switch (adc_feature->res_mode) {
|
|
case 8:
|
|
cfg_data |= VF610_ADC_MODE_BIT8;
|
|
break;
|
|
case 10:
|
|
cfg_data |= VF610_ADC_MODE_BIT10;
|
|
break;
|
|
case 12:
|
|
cfg_data |= VF610_ADC_MODE_BIT12;
|
|
break;
|
|
default:
|
|
dev_err(info->dev, "error resolution mode\n");
|
|
break;
|
|
}
|
|
|
|
/* clock select and clock divider */
|
|
cfg_data &= ~(VF610_ADC_CLK_MASK | VF610_ADC_ADCCLK_MASK);
|
|
switch (adc_feature->clk_div) {
|
|
case 1:
|
|
break;
|
|
case 2:
|
|
cfg_data |= VF610_ADC_CLK_DIV2;
|
|
break;
|
|
case 4:
|
|
cfg_data |= VF610_ADC_CLK_DIV4;
|
|
break;
|
|
case 8:
|
|
cfg_data |= VF610_ADC_CLK_DIV8;
|
|
break;
|
|
case 16:
|
|
switch (adc_feature->clk_sel) {
|
|
case VF610_ADCIOC_BUSCLK_SET:
|
|
cfg_data |= VF610_ADC_BUSCLK2_SEL | VF610_ADC_CLK_DIV8;
|
|
break;
|
|
default:
|
|
dev_err(info->dev, "error clk divider\n");
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Set ADLSMP and ADSTS based on the Long Sample Time Adder value
|
|
* determined.
|
|
*/
|
|
switch (adc_feature->lst_adder_index) {
|
|
case VF610_ADCK_CYCLES_3:
|
|
break;
|
|
case VF610_ADCK_CYCLES_5:
|
|
cfg_data |= VF610_ADC_ADSTS_SHORT;
|
|
break;
|
|
case VF610_ADCK_CYCLES_7:
|
|
cfg_data |= VF610_ADC_ADSTS_NORMAL;
|
|
break;
|
|
case VF610_ADCK_CYCLES_9:
|
|
cfg_data |= VF610_ADC_ADSTS_LONG;
|
|
break;
|
|
case VF610_ADCK_CYCLES_13:
|
|
cfg_data |= VF610_ADC_ADLSMP_LONG;
|
|
break;
|
|
case VF610_ADCK_CYCLES_17:
|
|
cfg_data |= VF610_ADC_ADLSMP_LONG;
|
|
cfg_data |= VF610_ADC_ADSTS_SHORT;
|
|
break;
|
|
case VF610_ADCK_CYCLES_21:
|
|
cfg_data |= VF610_ADC_ADLSMP_LONG;
|
|
cfg_data |= VF610_ADC_ADSTS_NORMAL;
|
|
break;
|
|
case VF610_ADCK_CYCLES_25:
|
|
cfg_data |= VF610_ADC_ADLSMP_LONG;
|
|
cfg_data |= VF610_ADC_ADSTS_NORMAL;
|
|
break;
|
|
default:
|
|
dev_err(info->dev, "error in sample time select\n");
|
|
}
|
|
|
|
/* update hardware average selection */
|
|
cfg_data &= ~VF610_ADC_AVGS_MASK;
|
|
gc_data &= ~VF610_ADC_AVGEN;
|
|
switch (adc_feature->sample_rate) {
|
|
case VF610_ADC_SAMPLE_1:
|
|
break;
|
|
case VF610_ADC_SAMPLE_4:
|
|
gc_data |= VF610_ADC_AVGEN;
|
|
break;
|
|
case VF610_ADC_SAMPLE_8:
|
|
gc_data |= VF610_ADC_AVGEN;
|
|
cfg_data |= VF610_ADC_AVGS_8;
|
|
break;
|
|
case VF610_ADC_SAMPLE_16:
|
|
gc_data |= VF610_ADC_AVGEN;
|
|
cfg_data |= VF610_ADC_AVGS_16;
|
|
break;
|
|
case VF610_ADC_SAMPLE_32:
|
|
gc_data |= VF610_ADC_AVGEN;
|
|
cfg_data |= VF610_ADC_AVGS_32;
|
|
break;
|
|
default:
|
|
dev_err(info->dev,
|
|
"error hardware sample average select\n");
|
|
}
|
|
|
|
writel(cfg_data, info->regs + VF610_REG_ADC_CFG);
|
|
writel(gc_data, info->regs + VF610_REG_ADC_GC);
|
|
}
|
|
|
|
static void vf610_adc_hw_init(struct vf610_adc *info)
|
|
{
|
|
/* CFG: Feature set */
|
|
vf610_adc_cfg_post_set(info);
|
|
vf610_adc_sample_set(info);
|
|
|
|
/* adc calibration */
|
|
vf610_adc_calibration(info);
|
|
|
|
/* CFG: power and speed set */
|
|
vf610_adc_cfg_set(info);
|
|
}
|
|
|
|
static int vf610_set_conversion_mode(struct iio_dev *indio_dev,
|
|
const struct iio_chan_spec *chan,
|
|
unsigned int mode)
|
|
{
|
|
struct vf610_adc *info = iio_priv(indio_dev);
|
|
|
|
mutex_lock(&indio_dev->mlock);
|
|
info->adc_feature.conv_mode = mode;
|
|
vf610_adc_calculate_rates(info);
|
|
vf610_adc_hw_init(info);
|
|
mutex_unlock(&indio_dev->mlock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vf610_get_conversion_mode(struct iio_dev *indio_dev,
|
|
const struct iio_chan_spec *chan)
|
|
{
|
|
struct vf610_adc *info = iio_priv(indio_dev);
|
|
|
|
return info->adc_feature.conv_mode;
|
|
}
|
|
|
|
static const char * const vf610_conv_modes[] = { "normal", "high-speed",
|
|
"low-power" };
|
|
|
|
static const struct iio_enum vf610_conversion_mode = {
|
|
.items = vf610_conv_modes,
|
|
.num_items = ARRAY_SIZE(vf610_conv_modes),
|
|
.get = vf610_get_conversion_mode,
|
|
.set = vf610_set_conversion_mode,
|
|
};
|
|
|
|
static const struct iio_chan_spec_ext_info vf610_ext_info[] = {
|
|
IIO_ENUM("conversion_mode", IIO_SHARED_BY_DIR, &vf610_conversion_mode),
|
|
{},
|
|
};
|
|
|
|
#define VF610_ADC_CHAN(_idx, _chan_type) { \
|
|
.type = (_chan_type), \
|
|
.indexed = 1, \
|
|
.channel = (_idx), \
|
|
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
|
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
|
|
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
|
|
.ext_info = vf610_ext_info, \
|
|
.scan_index = (_idx), \
|
|
.scan_type = { \
|
|
.sign = 'u', \
|
|
.realbits = 12, \
|
|
.storagebits = 16, \
|
|
}, \
|
|
}
|
|
|
|
#define VF610_ADC_TEMPERATURE_CHAN(_idx, _chan_type) { \
|
|
.type = (_chan_type), \
|
|
.channel = (_idx), \
|
|
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
|
|
.scan_index = (_idx), \
|
|
.scan_type = { \
|
|
.sign = 'u', \
|
|
.realbits = 12, \
|
|
.storagebits = 16, \
|
|
}, \
|
|
}
|
|
|
|
static const struct iio_chan_spec vf610_adc_iio_channels[] = {
|
|
VF610_ADC_CHAN(0, IIO_VOLTAGE),
|
|
VF610_ADC_CHAN(1, IIO_VOLTAGE),
|
|
VF610_ADC_CHAN(2, IIO_VOLTAGE),
|
|
VF610_ADC_CHAN(3, IIO_VOLTAGE),
|
|
VF610_ADC_CHAN(4, IIO_VOLTAGE),
|
|
VF610_ADC_CHAN(5, IIO_VOLTAGE),
|
|
VF610_ADC_CHAN(6, IIO_VOLTAGE),
|
|
VF610_ADC_CHAN(7, IIO_VOLTAGE),
|
|
VF610_ADC_CHAN(8, IIO_VOLTAGE),
|
|
VF610_ADC_CHAN(9, IIO_VOLTAGE),
|
|
VF610_ADC_CHAN(10, IIO_VOLTAGE),
|
|
VF610_ADC_CHAN(11, IIO_VOLTAGE),
|
|
VF610_ADC_CHAN(12, IIO_VOLTAGE),
|
|
VF610_ADC_CHAN(13, IIO_VOLTAGE),
|
|
VF610_ADC_CHAN(14, IIO_VOLTAGE),
|
|
VF610_ADC_CHAN(15, IIO_VOLTAGE),
|
|
VF610_ADC_TEMPERATURE_CHAN(26, IIO_TEMP),
|
|
IIO_CHAN_SOFT_TIMESTAMP(32),
|
|
/* sentinel */
|
|
};
|
|
|
|
static int vf610_adc_read_data(struct vf610_adc *info)
|
|
{
|
|
int result;
|
|
|
|
result = readl(info->regs + VF610_REG_ADC_R0);
|
|
|
|
switch (info->adc_feature.res_mode) {
|
|
case 8:
|
|
result &= 0xFF;
|
|
break;
|
|
case 10:
|
|
result &= 0x3FF;
|
|
break;
|
|
case 12:
|
|
result &= 0xFFF;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static irqreturn_t vf610_adc_isr(int irq, void *dev_id)
|
|
{
|
|
struct iio_dev *indio_dev = (struct iio_dev *)dev_id;
|
|
struct vf610_adc *info = iio_priv(indio_dev);
|
|
int coco;
|
|
|
|
coco = readl(info->regs + VF610_REG_ADC_HS);
|
|
if (coco & VF610_ADC_HS_COCO0) {
|
|
info->value = vf610_adc_read_data(info);
|
|
if (iio_buffer_enabled(indio_dev)) {
|
|
info->scan.chan = info->value;
|
|
iio_push_to_buffers_with_timestamp(indio_dev,
|
|
&info->scan,
|
|
iio_get_time_ns(indio_dev));
|
|
iio_trigger_notify_done(indio_dev->trig);
|
|
} else
|
|
complete(&info->completion);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static ssize_t vf610_show_samp_freq_avail(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct vf610_adc *info = iio_priv(dev_to_iio_dev(dev));
|
|
size_t len = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(info->sample_freq_avail); i++)
|
|
len += scnprintf(buf + len, PAGE_SIZE - len,
|
|
"%u ", info->sample_freq_avail[i]);
|
|
|
|
/* replace trailing space by newline */
|
|
buf[len - 1] = '\n';
|
|
|
|
return len;
|
|
}
|
|
|
|
static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(vf610_show_samp_freq_avail);
|
|
|
|
static struct attribute *vf610_attributes[] = {
|
|
&iio_dev_attr_sampling_frequency_available.dev_attr.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group vf610_attribute_group = {
|
|
.attrs = vf610_attributes,
|
|
};
|
|
|
|
static int vf610_read_raw(struct iio_dev *indio_dev,
|
|
struct iio_chan_spec const *chan,
|
|
int *val,
|
|
int *val2,
|
|
long mask)
|
|
{
|
|
struct vf610_adc *info = iio_priv(indio_dev);
|
|
unsigned int hc_cfg;
|
|
long ret;
|
|
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_RAW:
|
|
case IIO_CHAN_INFO_PROCESSED:
|
|
mutex_lock(&indio_dev->mlock);
|
|
if (iio_buffer_enabled(indio_dev)) {
|
|
mutex_unlock(&indio_dev->mlock);
|
|
return -EBUSY;
|
|
}
|
|
|
|
reinit_completion(&info->completion);
|
|
hc_cfg = VF610_ADC_ADCHC(chan->channel);
|
|
hc_cfg |= VF610_ADC_AIEN;
|
|
writel(hc_cfg, info->regs + VF610_REG_ADC_HC0);
|
|
ret = wait_for_completion_interruptible_timeout
|
|
(&info->completion, VF610_ADC_TIMEOUT);
|
|
if (ret == 0) {
|
|
mutex_unlock(&indio_dev->mlock);
|
|
return -ETIMEDOUT;
|
|
}
|
|
if (ret < 0) {
|
|
mutex_unlock(&indio_dev->mlock);
|
|
return ret;
|
|
}
|
|
|
|
switch (chan->type) {
|
|
case IIO_VOLTAGE:
|
|
*val = info->value;
|
|
break;
|
|
case IIO_TEMP:
|
|
/*
|
|
* Calculate in degree Celsius times 1000
|
|
* Using the typical sensor slope of 1.84 mV/°C
|
|
* and VREFH_ADC at 3.3V, V at 25°C of 699 mV
|
|
*/
|
|
*val = 25000 - ((int)info->value - VF610_VTEMP25_3V3) *
|
|
1000000 / VF610_TEMP_SLOPE_COEFF;
|
|
|
|
break;
|
|
default:
|
|
mutex_unlock(&indio_dev->mlock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_unlock(&indio_dev->mlock);
|
|
return IIO_VAL_INT;
|
|
|
|
case IIO_CHAN_INFO_SCALE:
|
|
*val = info->vref_uv / 1000;
|
|
*val2 = info->adc_feature.res_mode;
|
|
return IIO_VAL_FRACTIONAL_LOG2;
|
|
|
|
case IIO_CHAN_INFO_SAMP_FREQ:
|
|
*val = info->sample_freq_avail[info->adc_feature.sample_rate];
|
|
*val2 = 0;
|
|
return IIO_VAL_INT;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int vf610_write_raw(struct iio_dev *indio_dev,
|
|
struct iio_chan_spec const *chan,
|
|
int val,
|
|
int val2,
|
|
long mask)
|
|
{
|
|
struct vf610_adc *info = iio_priv(indio_dev);
|
|
int i;
|
|
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_SAMP_FREQ:
|
|
for (i = 0;
|
|
i < ARRAY_SIZE(info->sample_freq_avail);
|
|
i++)
|
|
if (val == info->sample_freq_avail[i]) {
|
|
info->adc_feature.sample_rate = i;
|
|
vf610_adc_sample_set(info);
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int vf610_adc_buffer_postenable(struct iio_dev *indio_dev)
|
|
{
|
|
struct vf610_adc *info = iio_priv(indio_dev);
|
|
unsigned int channel;
|
|
int ret;
|
|
int val;
|
|
|
|
ret = iio_triggered_buffer_postenable(indio_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val = readl(info->regs + VF610_REG_ADC_GC);
|
|
val |= VF610_ADC_ADCON;
|
|
writel(val, info->regs + VF610_REG_ADC_GC);
|
|
|
|
channel = find_first_bit(indio_dev->active_scan_mask,
|
|
indio_dev->masklength);
|
|
|
|
val = VF610_ADC_ADCHC(channel);
|
|
val |= VF610_ADC_AIEN;
|
|
|
|
writel(val, info->regs + VF610_REG_ADC_HC0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vf610_adc_buffer_predisable(struct iio_dev *indio_dev)
|
|
{
|
|
struct vf610_adc *info = iio_priv(indio_dev);
|
|
unsigned int hc_cfg = 0;
|
|
int val;
|
|
|
|
val = readl(info->regs + VF610_REG_ADC_GC);
|
|
val &= ~VF610_ADC_ADCON;
|
|
writel(val, info->regs + VF610_REG_ADC_GC);
|
|
|
|
hc_cfg |= VF610_ADC_CONV_DISABLE;
|
|
hc_cfg &= ~VF610_ADC_AIEN;
|
|
|
|
writel(hc_cfg, info->regs + VF610_REG_ADC_HC0);
|
|
|
|
return iio_triggered_buffer_predisable(indio_dev);
|
|
}
|
|
|
|
static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = {
|
|
.postenable = &vf610_adc_buffer_postenable,
|
|
.predisable = &vf610_adc_buffer_predisable,
|
|
.validate_scan_mask = &iio_validate_scan_mask_onehot,
|
|
};
|
|
|
|
static int vf610_adc_reg_access(struct iio_dev *indio_dev,
|
|
unsigned reg, unsigned writeval,
|
|
unsigned *readval)
|
|
{
|
|
struct vf610_adc *info = iio_priv(indio_dev);
|
|
|
|
if ((readval == NULL) ||
|
|
((reg % 4) || (reg > VF610_REG_ADC_PCTL)))
|
|
return -EINVAL;
|
|
|
|
*readval = readl(info->regs + reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct iio_info vf610_adc_iio_info = {
|
|
.driver_module = THIS_MODULE,
|
|
.read_raw = &vf610_read_raw,
|
|
.write_raw = &vf610_write_raw,
|
|
.debugfs_reg_access = &vf610_adc_reg_access,
|
|
.attrs = &vf610_attribute_group,
|
|
};
|
|
|
|
static const struct of_device_id vf610_adc_match[] = {
|
|
{ .compatible = "fsl,vf610-adc", },
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, vf610_adc_match);
|
|
|
|
static int vf610_adc_probe(struct platform_device *pdev)
|
|
{
|
|
struct vf610_adc *info;
|
|
struct iio_dev *indio_dev;
|
|
struct resource *mem;
|
|
int irq;
|
|
int ret;
|
|
|
|
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct vf610_adc));
|
|
if (!indio_dev) {
|
|
dev_err(&pdev->dev, "Failed allocating iio device\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
info = iio_priv(indio_dev);
|
|
info->dev = &pdev->dev;
|
|
|
|
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
info->regs = devm_ioremap_resource(&pdev->dev, mem);
|
|
if (IS_ERR(info->regs))
|
|
return PTR_ERR(info->regs);
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0) {
|
|
dev_err(&pdev->dev, "no irq resource?\n");
|
|
return irq;
|
|
}
|
|
|
|
ret = devm_request_irq(info->dev, irq,
|
|
vf610_adc_isr, 0,
|
|
dev_name(&pdev->dev), indio_dev);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "failed requesting irq, irq = %d\n", irq);
|
|
return ret;
|
|
}
|
|
|
|
info->clk = devm_clk_get(&pdev->dev, "adc");
|
|
if (IS_ERR(info->clk)) {
|
|
dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
|
|
PTR_ERR(info->clk));
|
|
return PTR_ERR(info->clk);
|
|
}
|
|
|
|
info->vref = devm_regulator_get(&pdev->dev, "vref");
|
|
if (IS_ERR(info->vref))
|
|
return PTR_ERR(info->vref);
|
|
|
|
ret = regulator_enable(info->vref);
|
|
if (ret)
|
|
return ret;
|
|
|
|
info->vref_uv = regulator_get_voltage(info->vref);
|
|
|
|
of_property_read_u32_array(pdev->dev.of_node, "fsl,adck-max-frequency",
|
|
info->max_adck_rate, 3);
|
|
|
|
ret = of_property_read_u32(pdev->dev.of_node, "min-sample-time",
|
|
&info->adc_feature.default_sample_time);
|
|
if (ret)
|
|
info->adc_feature.default_sample_time = DEFAULT_SAMPLE_TIME;
|
|
|
|
platform_set_drvdata(pdev, indio_dev);
|
|
|
|
init_completion(&info->completion);
|
|
|
|
indio_dev->name = dev_name(&pdev->dev);
|
|
indio_dev->dev.parent = &pdev->dev;
|
|
indio_dev->dev.of_node = pdev->dev.of_node;
|
|
indio_dev->info = &vf610_adc_iio_info;
|
|
indio_dev->modes = INDIO_DIRECT_MODE;
|
|
indio_dev->channels = vf610_adc_iio_channels;
|
|
indio_dev->num_channels = ARRAY_SIZE(vf610_adc_iio_channels);
|
|
|
|
ret = clk_prepare_enable(info->clk);
|
|
if (ret) {
|
|
dev_err(&pdev->dev,
|
|
"Could not prepare or enable the clock.\n");
|
|
goto error_adc_clk_enable;
|
|
}
|
|
|
|
vf610_adc_cfg_init(info);
|
|
vf610_adc_hw_init(info);
|
|
|
|
ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time,
|
|
NULL, &iio_triggered_buffer_setup_ops);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "Couldn't initialise the buffer\n");
|
|
goto error_iio_device_register;
|
|
}
|
|
|
|
ret = iio_device_register(indio_dev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Couldn't register the device.\n");
|
|
goto error_adc_buffer_init;
|
|
}
|
|
|
|
return 0;
|
|
|
|
error_adc_buffer_init:
|
|
iio_triggered_buffer_cleanup(indio_dev);
|
|
error_iio_device_register:
|
|
clk_disable_unprepare(info->clk);
|
|
error_adc_clk_enable:
|
|
regulator_disable(info->vref);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int vf610_adc_remove(struct platform_device *pdev)
|
|
{
|
|
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
|
struct vf610_adc *info = iio_priv(indio_dev);
|
|
|
|
iio_device_unregister(indio_dev);
|
|
iio_triggered_buffer_cleanup(indio_dev);
|
|
regulator_disable(info->vref);
|
|
clk_disable_unprepare(info->clk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int vf610_adc_suspend(struct device *dev)
|
|
{
|
|
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
|
struct vf610_adc *info = iio_priv(indio_dev);
|
|
int hc_cfg;
|
|
|
|
/* ADC controller enters to stop mode */
|
|
hc_cfg = readl(info->regs + VF610_REG_ADC_HC0);
|
|
hc_cfg |= VF610_ADC_CONV_DISABLE;
|
|
writel(hc_cfg, info->regs + VF610_REG_ADC_HC0);
|
|
|
|
clk_disable_unprepare(info->clk);
|
|
regulator_disable(info->vref);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vf610_adc_resume(struct device *dev)
|
|
{
|
|
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
|
struct vf610_adc *info = iio_priv(indio_dev);
|
|
int ret;
|
|
|
|
ret = regulator_enable(info->vref);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_prepare_enable(info->clk);
|
|
if (ret)
|
|
goto disable_reg;
|
|
|
|
vf610_adc_hw_init(info);
|
|
|
|
return 0;
|
|
|
|
disable_reg:
|
|
regulator_disable(info->vref);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static SIMPLE_DEV_PM_OPS(vf610_adc_pm_ops, vf610_adc_suspend, vf610_adc_resume);
|
|
|
|
static struct platform_driver vf610_adc_driver = {
|
|
.probe = vf610_adc_probe,
|
|
.remove = vf610_adc_remove,
|
|
.driver = {
|
|
.name = DRIVER_NAME,
|
|
.of_match_table = vf610_adc_match,
|
|
.pm = &vf610_adc_pm_ops,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(vf610_adc_driver);
|
|
|
|
MODULE_AUTHOR("Fugang Duan <B38611@freescale.com>");
|
|
MODULE_DESCRIPTION("Freescale VF610 ADC driver");
|
|
MODULE_LICENSE("GPL v2");
|