123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
- */
- #define pr_fmt(fmt) "AMOLED_ECM: %s: " fmt, __func__
- #include <linux/device.h>
- #include <linux/errno.h>
- #include <linux/hwmon.h>
- #include <linux/hwmon-sysfs.h>
- #include <linux/interrupt.h>
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/nvmem-consumer.h>
- #include <linux/of.h>
- #include <linux/of_irq.h>
- #include <linux/platform_device.h>
- #include <linux/regmap.h>
- #include <linux/workqueue.h>
- #include <linux/soc/qcom/panel_event_notifier.h>
- /* AMOLED AB register definitions */
- #define AB_REVISION2 0x01
- /* AMOLED ECM register definitions */
- #define AB_ECM_EN_CTL 0xA0
- #define ECM_EN BIT(7)
- #define AB_ECM_COUNTER_CTL 0xA1
- #define ECM_COUNTER_START BIT(7)
- /* AMOLED ECM SDAM Offsets */
- #define ECM_SDAM_START_BASE 0x40
- #define ECM_SDAMX_SAMPLE_START_ADDR 0x46
- #define ECM_FAULT_LOG 0x48
- #define ECM_ROUTINE_LOG 0x49
- #define ECM_ACTIVE_SDAM 0x4D
- #define ECM_SDAM0_ACTIVE BIT(0)
- #define ECM_SDAM1_ACTIVE BIT(1)
- #define ECM_SAMPLE_CNT_LSB 0x4E
- #define ECM_SAMPLE_CNT_MSB 0x4F
- #define ECM_STATUS_SET 0x50
- #define ECM_STATUS_CLR 0x51
- #define ECM_ONGOING BIT(0)
- #define ECM_DONE BIT(1)
- #define ECM_ABORT BIT(2)
- #define ECM_SDAM0_FULL BIT(3)
- #define ECM_SDAM1_FULL BIT(4)
- #define ECM_SDAM0_INDEX 0x52
- #define ECM_SDAM1_INDEX 0x53
- #define ECM_SDAM2_INDEX 0x61
- #define ECM_MODE 0x54
- #define ECM_CONTINUOUS 0
- #define ECM_N_ESWIRE 1
- #define ECM_M_ASWIRE 2
- #define ECM_ESWIRE_ASWIRE 3
- #define ECM_USE_TIMER 4
- #define ECM_N_ESWIRE_COUNT_LSB 0x55
- #define ECM_N_ESWIRE_COUNT_MSB 0x56
- #define ECM_M_ASWIRE_COUNT_LSB 0x57
- #define ECM_M_ASWIRE_COUNT_MSB 0x58
- #define ECM_ESWIRE_ASWIRE_SKIP_COUNT_LSB 0x59
- #define ECM_ESWIRE_ASWIRE_SKIP_COUNT_MSB 0x5A
- #define ECM_TIMER_LSB 0x5B
- #define ECM_TIMER_MSB 0x5C
- #define ECM_TIMER_SKIP_LSB 0x5D
- #define ECM_TIMER_SKIP_MSB 0x5E
- #define ECM_SEND_IRQ 0x5F
- #define SEND_SDAM0_IRQ BIT(0)
- #define SEND_SDAM1_IRQ BIT(1)
- #define SEND_SDAM2_IRQ BIT(2)
- #define ECM_WRITE_TO_SDAM 0x60
- #define WRITE_SDAM0_DATA BIT(0)
- #define WRITE_SDAM1_DATA BIT(1)
- #define WRITE_SDAM2_DATA BIT(2)
- #define OVERWRITE_SDAM0_DATA BIT(4)
- #define OVERWRITE_SDAM1_DATA BIT(5)
- #define OVERWRITE_SDAM2_DATA BIT(6)
- #define ECM_AVERAGE_LSB 0x61
- #define ECM_AVERAGE_MSB 0x62
- #define ECM_MIN_LSB 0x63
- #define ECM_MIN_MSB 0x64
- #define ECM_MAX_LSB 0x65
- #define ECM_MAX_MSB 0x66
- #define ECM_SDAM0_SAMPLE_START_ADDR 0x6C
- #define ECM_SDAM_SAMPLE_END_ADDR 0xBF
- /* ECM specific definitions */
- #define ECM_SAMPLE_GAIN_V1 15
- #define ECM_SAMPLE_GAIN_V2 16
- #define ECM_MIN_M_SAMPLES 10
- #define AMOLED_AB_REVISION_1P0 0
- #define AMOLED_AB_REVISION_2P0 1
- enum amoled_ecm_mode {
- ECM_MODE_CONTINUOUS = 0,
- ECM_MODE_MULTI_FRAMES,
- ECM_MODE_IDLE,
- };
- struct amoled_ecm_sdam_config {
- u8 reg;
- u8 reset_val;
- };
- /**
- * struct amoled_ecm_sdam - AMOLED ECM sdam data structure
- * @nvmem: Pointer to nvmem device
- * @start_addr: Start address of ECM samples in SDAM
- * @irq_name: Interrupt name for SDAM
- * @irq: Interrupt associated with the SDAM
- */
- struct amoled_ecm_sdam {
- struct nvmem_device *nvmem;
- u32 start_addr;
- const char *irq_name;
- int irq;
- };
- /**
- * struct amoled_ecm_data - Structure for AMOLED ECM data
- * @m_cumulative: Cumulative of M sample values
- * @num_m_samples: Number of M samples available
- * @time_period_ms: Time period(in milli seconds) for ECM request
- * @frames: Number of frames for ECM request
- * @avg_current: AMOLED ECM average calculated
- * @mode: AMOLED ECM mode of operation
- */
- struct amoled_ecm_data {
- unsigned long long m_cumulative;
- u32 num_m_samples;
- u32 time_period_ms;
- u16 frames;
- u16 avg_current;
- enum amoled_ecm_mode mode;
- };
- /**
- * struct amoled ecm - Structure for AMOLED ECM device
- * @regmap: Pointer for regmap structure
- * @dev: Pointer for AMOLED ECM device
- * @data: AMOLED ECM data structure
- * @sdam: Pointer for array of ECM sdams
- * @sdam_lock: Locking for mutual exclusion
- * @average_work: Delayed work to calculate ECM average
- * @active_panel: Active DRM panel which sends panel notifications
- * @notifier_cookie: The cookie from panel notifier
- * @num_sdams: Number of SDAMs used for AMOLED ECM
- * @base: Base address of the AMOLED ECM module
- * @ab_revision: Revision of the AMOLED AB module
- * @enable: Flag to enable/disable AMOLED ECM
- * @abort: Flag to indicated AMOLED ECM has aborted
- * @reenable: Flag to reenable ECM when display goes unblank
- */
- struct amoled_ecm {
- struct regmap *regmap;
- struct device *dev;
- struct amoled_ecm_data data;
- struct amoled_ecm_sdam *sdam;
- struct mutex sdam_lock;
- struct delayed_work average_work;
- struct drm_panel *active_panel;
- void *notifier_cookie;
- u32 num_sdams;
- u32 base;
- u8 ab_revision;
- bool enable;
- bool abort;
- bool reenable;
- };
- static struct amoled_ecm_sdam_config ecm_reset_config[] = {
- { ECM_FAULT_LOG, 0x00 },
- { ECM_ROUTINE_LOG, 0x00 },
- { ECM_ACTIVE_SDAM, 0x01 },
- { ECM_SAMPLE_CNT_LSB, 0x00 },
- { ECM_SAMPLE_CNT_MSB, 0x00 },
- { ECM_STATUS_SET, 0x00 },
- { ECM_STATUS_CLR, 0xFF },
- { ECM_SDAM0_INDEX, 0x6C },
- { ECM_SDAM1_INDEX, 0x46 },
- { ECM_SDAM2_INDEX, 0x46 },
- { ECM_MODE, 0x00 },
- };
- static int ecm_nvmem_device_write(struct nvmem_device *nvmem,
- unsigned int offset,
- size_t bytes, void *buf)
- {
- size_t i;
- u8 *ptr = buf;
- for (i = 0; i < bytes; i++)
- pr_debug("Wrote %#x to %#x\n", *ptr++, offset + i);
- return nvmem_device_write(nvmem, offset, bytes, buf);
- }
- static int ecm_reset_sdam_config(struct amoled_ecm *ecm)
- {
- int rc, i;
- u8 val = 0, val2 = 0;
- for (i = 0; i < ARRAY_SIZE(ecm_reset_config); i++) {
- rc = ecm_nvmem_device_write(ecm->sdam[0].nvmem,
- ecm_reset_config[i].reg,
- 1, &ecm_reset_config[i].reset_val);
- if (rc < 0) {
- pr_err("Failed to write %u to SDAM, rc=%d\n",
- ecm_reset_config[i].reg, rc);
- return rc;
- }
- }
- for (i = 0; i < ecm->num_sdams; i++) {
- val |= (SEND_SDAM0_IRQ << i);
- val2 |= (WRITE_SDAM0_DATA << i);
- }
- rc = ecm_nvmem_device_write(ecm->sdam[0].nvmem, ECM_SEND_IRQ, 1, &val);
- if (rc < 0) {
- pr_err("Failed to write %u to ECM_SEND_IRQ, rc=%d\n", val, rc);
- return rc;
- }
- rc = ecm_nvmem_device_write(ecm->sdam[0].nvmem, ECM_WRITE_TO_SDAM, 1,
- &val2);
- if (rc < 0)
- pr_err("Failed to write %u to ECM_WRITE_TO_SDAM, rc=%d\n", val2,
- rc);
- usleep_range(10000, 12000);
- return rc;
- }
- static int amoled_ecm_enable(struct amoled_ecm *ecm)
- {
- struct amoled_ecm_data *data = &ecm->data;
- int rc;
- if (data->frames) {
- rc = ecm_nvmem_device_write(ecm->sdam[0].nvmem,
- ECM_N_ESWIRE_COUNT_LSB, 2, &data->frames);
- if (rc < 0) {
- pr_err("Failed to write swire count to SDAM, rc=%d\n",
- rc);
- return rc;
- }
- data->mode = ECM_MODE_MULTI_FRAMES;
- } else {
- if (!data->time_period_ms)
- return -EINVAL;
- data->mode = ECM_MODE_CONTINUOUS;
- }
- if ((ecm->ab_revision != AMOLED_AB_REVISION_1P0) &&
- (ecm->ab_revision != AMOLED_AB_REVISION_2P0)) {
- pr_err("ECM is not supported for AB version %u\n",
- ecm->ab_revision);
- return -ENODEV;
- }
- rc = ecm_reset_sdam_config(ecm);
- if (rc < 0) {
- pr_err("Failed to reset ECM SDAM configuration, rc=%d\n", rc);
- return rc;
- }
- rc = ecm_nvmem_device_write(ecm->sdam[0].nvmem, ECM_MODE, 1,
- &data->mode);
- if (rc < 0) {
- pr_err("Failed to write ECM mode to SDAM, rc=%d\n", rc);
- return rc;
- }
- rc = regmap_write(ecm->regmap, ecm->base + AB_ECM_EN_CTL, ECM_EN);
- if (rc < 0) {
- pr_err("Failed to enable ECM, rc=%d\n", rc);
- return rc;
- }
- rc = regmap_write(ecm->regmap, ecm->base + AB_ECM_COUNTER_CTL,
- ECM_COUNTER_START);
- if (rc < 0) {
- pr_err("Failed to enable ECM counter, rc=%d\n", rc);
- return rc;
- }
- if (data->mode == ECM_MODE_CONTINUOUS)
- schedule_delayed_work(&ecm->average_work,
- msecs_to_jiffies(data->time_period_ms));
- ecm->enable = true;
- return rc;
- }
- static int amoled_ecm_disable(struct amoled_ecm *ecm)
- {
- int rc;
- rc = regmap_write(ecm->regmap, ecm->base + AB_ECM_COUNTER_CTL, 0);
- if (rc < 0) {
- pr_err("Failed to disable ECM counter, rc=%d\n", rc);
- return rc;
- }
- rc = regmap_write(ecm->regmap, ecm->base + AB_ECM_EN_CTL, 0);
- if (rc < 0) {
- pr_err("Failed to disable ECM, rc=%d\n", rc);
- return rc;
- }
- rc = ecm_nvmem_device_write(ecm->sdam[0].nvmem, ECM_AVERAGE_LSB, 2,
- &ecm->data.avg_current);
- if (rc < 0) {
- pr_err("Failed to write ECM average to SDAM, rc=%d\n", rc);
- return rc;
- }
- pr_debug("ECM_AVERAGE:%u\n", ecm->data.avg_current);
- cancel_delayed_work(&ecm->average_work);
- ecm->data.avg_current = 0;
- ecm->data.m_cumulative = 0;
- ecm->data.num_m_samples = 0;
- ecm->data.mode = ECM_MODE_IDLE;
- ecm->abort = false;
- ecm->enable = false;
- return rc;
- }
- static void ecm_average_work(struct work_struct *work)
- {
- struct amoled_ecm *ecm = container_of(work, struct amoled_ecm,
- average_work.work);
- struct amoled_ecm_data *data = &ecm->data;
- mutex_lock(&ecm->sdam_lock);
- if (!data->num_m_samples || !data->m_cumulative) {
- pr_warn_ratelimited("Invalid data, num_m_samples=%u m_cumulative:%u\n",
- data->num_m_samples, data->m_cumulative);
- data->avg_current = 0;
- } else {
- data->avg_current = data->m_cumulative / data->num_m_samples;
- pr_debug("avg_current=%u mA\n", data->avg_current);
- }
- data->m_cumulative = 0;
- data->num_m_samples = 0;
- mutex_unlock(&ecm->sdam_lock);
- /*
- * If ECM is not aborted and still enabled, run it one more time
- */
- if (!ecm->abort && ecm->enable)
- schedule_delayed_work(&ecm->average_work,
- msecs_to_jiffies(ecm->data.time_period_ms));
- }
- static ssize_t enable_show(struct device *dev,
- struct device_attribute *attr, char *buf)
- {
- struct amoled_ecm *ecm = dev_get_drvdata(dev);
- return scnprintf(buf, PAGE_SIZE, "%d\n", ecm->enable);
- }
- static ssize_t enable_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
- {
- struct amoled_ecm *ecm = dev_get_drvdata(dev);
- bool val;
- int rc;
- rc = kstrtobool(buf, &val);
- if (rc < 0)
- return rc;
- if (ecm->enable == val) {
- pr_err("AMOLED ECM is already %s\n",
- val ? "enabled" : "disabled");
- return -EINVAL;
- }
- if (val) {
- rc = amoled_ecm_enable(ecm);
- if (rc < 0) {
- pr_err("Failed to enable AMOLED ECM, rc=%d\n", rc);
- return rc;
- }
- } else {
- rc = amoled_ecm_disable(ecm);
- if (rc < 0) {
- pr_err("Failed to disable AMOLED ECM, rc=%d\n", rc);
- return rc;
- }
- ecm->data.frames = 0;
- ecm->data.time_period_ms = 0;
- }
- return count;
- }
- static DEVICE_ATTR_RW(enable);
- static ssize_t frames_show(struct device *dev,
- struct device_attribute *attr, char *buf)
- {
- struct amoled_ecm *ecm = dev_get_drvdata(dev);
- return scnprintf(buf, PAGE_SIZE, "%u\n", ecm->data.frames);
- }
- static ssize_t frames_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
- {
- struct amoled_ecm *ecm = dev_get_drvdata(dev);
- u16 val;
- int rc;
- if (ecm->enable) {
- pr_err("Failed to configure frames, ECM is already running\n");
- return -EINVAL;
- }
- rc = kstrtou16(buf, 0, &val);
- if ((rc < 0) || !val)
- return -EINVAL;
- ecm->data.frames = val;
- return count;
- }
- static DEVICE_ATTR_RW(frames);
- static ssize_t time_period_show(struct device *dev,
- struct device_attribute *attr, char *buf)
- {
- struct amoled_ecm *ecm = dev_get_drvdata(dev);
- return scnprintf(buf, PAGE_SIZE, "%u\n", ecm->data.time_period_ms);
- }
- static ssize_t time_period_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
- {
- struct amoled_ecm *ecm = dev_get_drvdata(dev);
- u32 val;
- int rc;
- if (ecm->enable) {
- pr_err("Failed to configure time_period, ECM is already running\n");
- return -EINVAL;
- }
- rc = kstrtou32(buf, 0, &val);
- if ((rc < 0) || !val)
- return -EINVAL;
- ecm->data.time_period_ms = val;
- return count;
- }
- static DEVICE_ATTR_RW(time_period);
- static ssize_t avg_current_show(struct device *dev,
- struct device_attribute *attr, char *buf)
- {
- struct amoled_ecm *ecm = dev_get_drvdata(dev);
- return scnprintf(buf, PAGE_SIZE, "%u\n", ecm->data.avg_current);
- }
- static DEVICE_ATTR_RO(avg_current);
- static struct attribute *amoled_ecm_attrs[] = {
- &dev_attr_enable.attr,
- &dev_attr_frames.attr,
- &dev_attr_time_period.attr,
- &dev_attr_avg_current.attr,
- NULL,
- };
- static const struct attribute_group amoled_ecm_group = {
- .name = "amoled_ecm",
- .attrs = amoled_ecm_attrs,
- };
- __ATTRIBUTE_GROUPS(amoled_ecm);
- static int get_sdam_from_irq(struct amoled_ecm *ecm, int irq)
- {
- int i;
- for (i = 0; i < ecm->num_sdams; i++)
- if (ecm->sdam[i].irq == irq)
- return i;
- return -ENOENT;
- }
- static int handle_ecm_abort(struct amoled_ecm *ecm)
- {
- struct amoled_ecm_data *data = &ecm->data;
- int rc;
- u8 mode = data->mode;
- switch (mode) {
- case ECM_MODE_MULTI_FRAMES:
- pr_warn_ratelimited("Multiple frames mode is not supported\n");
- data->avg_current = 0;
- break;
- case ECM_MODE_CONTINUOUS:
- if (data->num_m_samples < ECM_MIN_M_SAMPLES) {
- pr_warn_ratelimited("Too few samples %u for continuous mode\n",
- data->num_m_samples);
- data->avg_current = 0;
- break;
- }
- ecm->abort = true;
- schedule_delayed_work(&ecm->average_work, 0);
- break;
- default:
- pr_err_ratelimited("Invalid ECM operation mode: %u\n", mode);
- data->avg_current = 0;
- return -EINVAL;
- }
- rc = amoled_ecm_disable(ecm);
- if (rc < 0)
- pr_err("Failed to disable AMOLED ECM, rc=%d\n", rc);
- return rc;
- }
- static int get_sdam_index(struct nvmem_device *nvmem, int sdam_num, u8 *index)
- {
- unsigned int addr;
- switch (sdam_num) {
- case 0:
- addr = ECM_SDAM0_INDEX;
- break;
- case 1:
- addr = ECM_SDAM1_INDEX;
- break;
- case 2:
- addr = ECM_SDAM2_INDEX;
- break;
- default:
- return -EINVAL;
- }
- return nvmem_device_read(nvmem, addr, 1, index);
- }
- static irqreturn_t sdam_full_irq_handler(int irq, void *_ecm)
- {
- struct amoled_ecm *ecm = _ecm;
- struct amoled_ecm_data *data = &ecm->data;
- u64 cumulative = 0, m_sample;
- int rc, i, sdam_num, sdam_start, num_ecm_samples, max_samples;
- u16 ecm_sample, gain;
- u8 buf[2], int_status, sdam_index, overwrite;
- sdam_num = get_sdam_from_irq(ecm, irq);
- if (sdam_num < 0) {
- pr_err("Invalid SDAM interrupt, err=%d\n", sdam_num);
- return IRQ_HANDLED;
- }
- rc = nvmem_device_read(ecm->sdam[0].nvmem, ECM_STATUS_SET, 1,
- &int_status);
- if (rc < 0) {
- pr_err("Failed to read interrupt status from SDAM, rc=%d\n",
- rc);
- return IRQ_HANDLED;
- }
- pr_debug("ECM_STATUS_SET: %#x\n", int_status);
- if (data->mode != ECM_MODE_CONTINUOUS &&
- data->mode != ECM_MODE_MULTI_FRAMES)
- return IRQ_HANDLED;
- if (int_status & ECM_ABORT) {
- rc = handle_ecm_abort(ecm);
- if (rc < 0) {
- pr_err("Failed to handle ECM_ABORT interrupt, rc=%d\n",
- rc);
- return IRQ_HANDLED;
- }
- }
- rc = get_sdam_index(ecm->sdam[0].nvmem, sdam_num, &sdam_index);
- if (rc < 0) {
- pr_err("Failed to read SDAM index, rc=%d\n", rc);
- goto irq_exit;
- }
- pr_debug("sdam_num:%d sdam_index:%#x\n", sdam_num, sdam_index);
- sdam_start = ecm->sdam[sdam_num].start_addr;
- max_samples = (ECM_SDAM_SAMPLE_END_ADDR + 1 - sdam_start) / 2;
- num_ecm_samples = (sdam_index + 1 - sdam_start) / 2;
- if (!num_ecm_samples || (num_ecm_samples > max_samples)) {
- pr_err("Incorrect number of ECM samples, num_ecm_samples:%d max_samples:%d\n",
- num_ecm_samples, max_samples);
- return IRQ_HANDLED;
- }
- mutex_lock(&ecm->sdam_lock);
- rc = nvmem_device_read(ecm->sdam[0].nvmem, ECM_WRITE_TO_SDAM, 1,
- &overwrite);
- if (rc < 0) {
- pr_err("Failed to read ECM_WRITE_TO_SDAM from SDAM, rc=%d\n",
- rc);
- goto irq_exit;
- }
- overwrite &= ~(OVERWRITE_SDAM0_DATA << sdam_num);
- rc = ecm_nvmem_device_write(ecm->sdam[0].nvmem, ECM_WRITE_TO_SDAM,
- 1, &overwrite);
- if (rc < 0) {
- pr_err("Failed to write ECM_WRITE_TO_SDAM to SDAM, rc=%d\n",
- rc);
- goto irq_exit;
- }
- /*
- * For AMOLED AB peripheral,
- * Revision 1.0:
- * ECM measured current = 15 times of each LSB
- *
- * Revision 2.0:
- * ECM measured current = 16 times of each LSB
- */
- if (ecm->ab_revision == AMOLED_AB_REVISION_1P0)
- gain = ECM_SAMPLE_GAIN_V1;
- else
- gain = ECM_SAMPLE_GAIN_V2;
- for (i = sdam_start; i < sdam_index; i += 2) {
- rc = nvmem_device_read(ecm->sdam[sdam_num].nvmem, i, 2, buf);
- if (rc < 0) {
- pr_err("Failed to read SDAM sample, rc=%d\n", rc);
- goto irq_exit;
- }
- ecm_sample = (buf[1] << 8) | buf[0];
- cumulative += ((ecm_sample * 1000) / gain) / 1000;
- }
- overwrite |= (OVERWRITE_SDAM0_DATA << sdam_num);
- rc = ecm_nvmem_device_write(ecm->sdam[0].nvmem, ECM_WRITE_TO_SDAM,
- 1, &overwrite);
- if (rc < 0) {
- pr_err("Failed to write ECM_WRITE_TO_SDAM to SDAM, rc=%d\n",
- rc);
- goto irq_exit;
- }
- if (!cumulative) {
- pr_err("Error, No ECM samples captured. Cumulative:%lu\n",
- cumulative);
- goto irq_exit;
- }
- m_sample = cumulative / num_ecm_samples;
- data->m_cumulative += m_sample;
- data->num_m_samples++;
- buf[0] = (ECM_SDAM0_FULL << sdam_num);
- rc = ecm_nvmem_device_write(ecm->sdam[0].nvmem, ECM_STATUS_CLR, 1,
- &buf[0]);
- if (rc < 0) {
- pr_err("Failed to clear interrupt status in SDAM, rc=%d\n",
- rc);
- goto irq_exit;
- }
- if ((data->mode == ECM_MODE_MULTI_FRAMES) &&
- (sdam_index < max_samples))
- schedule_delayed_work(&ecm->average_work, 0);
- irq_exit:
- mutex_unlock(&ecm->sdam_lock);
- return IRQ_HANDLED;
- }
- static int amoled_ecm_parse_dt(struct amoled_ecm *ecm)
- {
- int rc = 0, i;
- u32 val;
- u8 buf[20];
- rc = of_property_read_u32(ecm->dev->of_node, "reg", &val);
- if (rc < 0) {
- pr_err("Failed to get reg, rc = %d\n", rc);
- return rc;
- }
- ecm->base = val;
- rc = of_property_count_strings(ecm->dev->of_node, "nvmem-names");
- if (rc < 0) {
- pr_err("Could not find nvmem device\n");
- return rc;
- }
- ecm->num_sdams = rc;
- ecm->sdam = devm_kcalloc(ecm->dev, ecm->num_sdams,
- sizeof(*ecm->sdam), GFP_KERNEL);
- if (!ecm->sdam)
- return -ENOMEM;
- for (i = 0; i < ecm->num_sdams; i++) {
- scnprintf(buf, sizeof(buf), "ecm-sdam%d", i);
- rc = of_irq_get_byname(ecm->dev->of_node, buf);
- if (rc < 0) {
- pr_err("Failed to get irq for ecm sdam, err=%d\n", rc);
- return -EINVAL;
- }
- ecm->sdam[i].irq_name = devm_kstrdup(ecm->dev, buf,
- GFP_KERNEL);
- if (!ecm->sdam[i].irq_name)
- return -ENOMEM;
- ecm->sdam[i].irq = rc;
- scnprintf(buf, sizeof(buf), "amoled-ecm-sdam%d", i);
- ecm->sdam[i].nvmem = devm_nvmem_device_get(ecm->dev, buf);
- if (IS_ERR(ecm->sdam[i].nvmem)) {
- rc = PTR_ERR(ecm->sdam[i].nvmem);
- if (rc != -EPROBE_DEFER)
- pr_err("Failed to get nvmem device, rc=%d\n",
- rc);
- ecm->sdam[i].nvmem = NULL;
- return rc;
- }
- }
- return 0;
- }
- #if IS_ENABLED(CONFIG_QCOM_PANEL_EVENT_NOTIFIER)
- static void panel_event_notifier_callback(enum panel_event_notifier_tag tag,
- struct panel_event_notification *notification, void *data)
- {
- struct amoled_ecm *ecm = data;
- int rc;
- if (!notification) {
- pr_err("Invalid panel notification\n");
- return;
- }
- pr_debug("panel event received, type: %d\n", notification->notif_type);
- switch (notification->notif_type) {
- case DRM_PANEL_EVENT_BLANK:
- if (ecm->enable) {
- rc = amoled_ecm_disable(ecm);
- if (rc < 0) {
- pr_err("Failed to disable ECM for display BLANK, rc=%d\n",
- rc);
- return;
- }
- ecm->reenable = true;
- pr_debug("Disabled ECM for display BLANK\n");
- }
- break;
- case DRM_PANEL_EVENT_UNBLANK:
- if (ecm->reenable) {
- rc = amoled_ecm_enable(ecm);
- if (rc < 0) {
- pr_err("Failed to re-enable ECM for display UNBLANK, rc=%d\n",
- rc);
- return;
- }
- ecm->reenable = false;
- pr_debug("Enabled ECM for display UNBLANK\n");
- }
- break;
- default:
- pr_debug("Ignore panel event: %d\n", notification->notif_type);
- break;
- }
- }
- static int qti_amoled_register_panel_notifier(struct amoled_ecm *ecm)
- {
- struct device_node *np = ecm->dev->of_node;
- struct device_node *pnode;
- struct drm_panel *panel;
- void *cookie = NULL;
- int i, count, rc;
- count = of_count_phandle_with_args(np, "display-panels", NULL);
- if (count <= 0)
- return 0;
- for (i = 0; i < count; i++) {
- pnode = of_parse_phandle(np, "display-panels", i);
- if (!pnode)
- return -ENODEV;
- panel = of_drm_find_panel(pnode);
- of_node_put(pnode);
- if (!IS_ERR(panel)) {
- ecm->active_panel = panel;
- break;
- }
- }
- if (!ecm->active_panel) {
- rc = PTR_ERR(panel);
- if (rc != -EPROBE_DEFER)
- pr_err("failed to find active panel, rc=%d\n", rc);
- return rc;
- }
- cookie = panel_event_notifier_register(
- PANEL_EVENT_NOTIFICATION_PRIMARY,
- PANEL_EVENT_NOTIFIER_CLIENT_ECM,
- ecm->active_panel,
- panel_event_notifier_callback,
- (void *)ecm);
- if (IS_ERR(cookie)) {
- rc = PTR_ERR(cookie);
- pr_err("failed to register panel event notifier, rc=%d\n", rc);
- return rc;
- }
- pr_debug("register panel notifier successfully\n");
- ecm->notifier_cookie = cookie;
- return 0;
- }
- static int qti_amoled_unregister_panel_notifier(struct amoled_ecm *ecm)
- {
- if (ecm->notifier_cookie)
- panel_event_notifier_unregister(ecm->notifier_cookie);
- return 0;
- }
- #else
- static inline int qti_amoled_register_panel_notifier(struct amoled_ecm *ecm)
- {
- return 0;
- }
- static inline int qti_amoled_unregister_panel_notifier(struct amoled_ecm *ecm)
- {
- return 0;
- }
- #endif
- static int qti_amoled_ecm_probe(struct platform_device *pdev)
- {
- struct device *hwmon_dev;
- struct amoled_ecm *ecm;
- int rc, i;
- unsigned int temp;
- ecm = devm_kzalloc(&pdev->dev, sizeof(*ecm), GFP_KERNEL);
- if (!ecm)
- return -ENOMEM;
- ecm->dev = &pdev->dev;
- ecm->regmap = dev_get_regmap(pdev->dev.parent, NULL);
- if (!ecm->regmap) {
- dev_err(&pdev->dev, "Failed to get regmap\n");
- return -EINVAL;
- }
- rc = amoled_ecm_parse_dt(ecm);
- if (rc < 0) {
- dev_err(&pdev->dev, "Failed to parse AMOLED ECM rc=%d\n", rc);
- return rc;
- }
- rc = regmap_read(ecm->regmap, ecm->base + AB_REVISION2, &temp);
- if (rc < 0) {
- dev_err(&pdev->dev, "Failed to read AB revision, rc=%d\n", rc);
- return rc;
- }
- ecm->ab_revision = temp;
- ecm->enable = false;
- ecm->abort = false;
- ecm->data.m_cumulative = 0;
- ecm->data.num_m_samples = 0;
- ecm->data.time_period_ms = 0;
- ecm->data.frames = 0;
- ecm->data.avg_current = 0;
- ecm->data.mode = ECM_MODE_IDLE;
- INIT_DELAYED_WORK(&ecm->average_work, ecm_average_work);
- mutex_init(&ecm->sdam_lock);
- dev_set_drvdata(ecm->dev, ecm);
- for (i = 0; i < ecm->num_sdams; i++) {
- rc = devm_request_threaded_irq(ecm->dev, ecm->sdam[i].irq,
- NULL, sdam_full_irq_handler,
- IRQF_ONESHOT, ecm->sdam[i].irq_name, ecm);
- if (rc < 0) {
- dev_err(&pdev->dev, "Failed to request IRQ(%s), rc=%d\n",
- ecm->sdam[i].irq_name, rc);
- return rc;
- }
- ecm->sdam[i].start_addr = i ? ECM_SDAMX_SAMPLE_START_ADDR
- : ECM_SDAM0_SAMPLE_START_ADDR;
- }
- hwmon_dev = devm_hwmon_device_register_with_groups(&pdev->dev,
- "amoled_ecm", ecm, amoled_ecm_groups);
- if (IS_ERR_OR_NULL(hwmon_dev)) {
- rc = PTR_ERR(hwmon_dev);
- pr_err("failed to register hwmon device for amoled-ecm, rc=%d\n",
- rc);
- return rc;
- }
- return qti_amoled_register_panel_notifier(ecm);
- }
- static int qti_amoled_ecm_remove(struct platform_device *pdev)
- {
- struct amoled_ecm *ecm = dev_get_drvdata(&pdev->dev);
- return qti_amoled_unregister_panel_notifier(ecm);
- }
- static const struct of_device_id amoled_ecm_match_table[] = {
- { .compatible = "qcom,amoled-ecm", },
- { },
- };
- static struct platform_driver qti_amoled_ecm_driver = {
- .driver = {
- .name = "qti_amoled_ecm",
- .of_match_table = amoled_ecm_match_table,
- },
- .probe = qti_amoled_ecm_probe,
- .remove = qti_amoled_ecm_remove,
- };
- module_platform_driver(qti_amoled_ecm_driver);
- MODULE_DESCRIPTION("QTI AMOLED ECM driver");
- MODULE_LICENSE("GPL v2");
|